diff --git a/examples/sites/demos/apis/nav-menu.js b/examples/sites/demos/apis/nav-menu.js
index e69deb86f8..db24ba1b4c 100644
--- a/examples/sites/demos/apis/nav-menu.js
+++ b/examples/sites/demos/apis/nav-menu.js
@@ -127,6 +127,16 @@ export default {
},
mode: ['pc'],
pcDemo: 'slot-toolbar'
+ },
+ {
+ name: 'icon',
+ defaultValue: '',
+ desc: {
+ 'zh-CN': '自定义菜单图标插槽',
+ 'en-US': 'Customize the menu icon'
+ },
+ mode: ['pc'],
+ pcDemo: 'menu-icon'
}
]
}
@@ -153,6 +163,7 @@ interface IMenuItem {
interface IDataItem {
title: string
url: string
+ icon?: Commonent
children?: IDataItem[]
}`
},
diff --git a/examples/sites/demos/pc/app/nav-menu/menu-icon-composition-api.vue b/examples/sites/demos/pc/app/nav-menu/menu-icon-composition-api.vue
new file mode 100644
index 0000000000..a92af90015
--- /dev/null
+++ b/examples/sites/demos/pc/app/nav-menu/menu-icon-composition-api.vue
@@ -0,0 +1,150 @@
+
+ 配置模式
+ slot 模式
+ icon
字段配置展示菜单图标。',
+ 'en-US': 'Display menu icon through icon
field of data.'
+ },
+ codeFiles: ['menu-icon.vue']
+ },
{
demoId: 'before-skip',
name: {
diff --git a/packages/renderless/src/nav-menu/index.ts b/packages/renderless/src/nav-menu/index.ts
index f1fc459e5a..19fe1dd1bd 100644
--- a/packages/renderless/src/nav-menu/index.ts
+++ b/packages/renderless/src/nav-menu/index.ts
@@ -169,7 +169,8 @@ export const initData =
id: item.id,
pid: item.pid,
isFullUrl: props.allowFullUrl && isFullUrl(router),
- target: item.target
+ target: item.target,
+ icon: item.icon
}
}
@@ -577,7 +578,7 @@ export const handleTitleMouseenter =
const text = target.textContent
const font = window.getComputedStyle(target).font
const rect = target.getBoundingClientRect()
- const res = omitText(text, font, rect.width + 2)
+ const res = omitText(text, font, rect.width + 4)
if (target && res.o) {
const tooltip = vm.$refs.tooltip
diff --git a/packages/renderless/types/nav-menu.type.ts b/packages/renderless/types/nav-menu.type.ts
index 520badb5f7..4082d1d4b1 100644
--- a/packages/renderless/types/nav-menu.type.ts
+++ b/packages/renderless/types/nav-menu.type.ts
@@ -13,6 +13,7 @@ export interface menuItemType {
url: string
children?: menuItemType[]
target?: string
+ icon?: any
}
export interface whitchSubMenuType {
diff --git a/packages/theme/src/nav-menu/index.less b/packages/theme/src/nav-menu/index.less
index 01406692b6..30dba8311f 100644
--- a/packages/theme/src/nav-menu/index.less
+++ b/packages/theme/src/nav-menu/index.less
@@ -59,18 +59,37 @@
line-height: var(--tv-NavMenu-height);
box-sizing: border-box;
+ .menu-icon {
+ display: inline-block;
+
+ svg {
+ vertical-align: text-bottom;
+ margin-right: var(--tv-NavMenu-icon-margin-right);
+ font-size: var(--tv-NavMenu-icon-size);
+ fill: var(--tv-NavMenu-icon-color);
+ }
+ }
+
&.active,
&:hover {
// 规范选中后,背景色修改
color: var(--tv-NavMenu-item-text-color);
text-decoration: none;
font-weight: var(--tv-NavMenu-item-text-font-weight);
+
+ svg {
+ fill: var(--tv-NavMenu-icon-color-hover);
+ }
}
&.selected {
position: relative;
font-weight: var(--tv-NavMenu-item-text-font-weight);
+ svg {
+ fill: var(--tv-NavMenu-icon-color-hover);
+ }
+
&:after {
content: ' ';
width: 100%;
@@ -179,6 +198,10 @@
&.selected {
position: relative;
+ svg {
+ fill: var(--tv-NavMenu-icon-color-hover) !important;
+ }
+
&:before {
content: '';
width: var(--tv-NavMenu-popmenu-more-item-before-width);
@@ -206,6 +229,17 @@
overflow: hidden;
text-overflow: ellipsis;
+ .menu-icon {
+ display: inline-block;
+
+ svg {
+ vertical-align: text-bottom;
+ margin-right: var(--tv-NavMenu-icon-margin-right);
+ font-size: var(--tv-NavMenu-icon-size);
+ fill: var(--tv-NavMenu-icon-color);
+ }
+ }
+
&.showicon {
width: calc(100% - 12px);
}
@@ -253,11 +287,26 @@
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
+
+ .menu-icon {
+ display: inline-block;
+
+ svg {
+ vertical-align: text-bottom;
+ margin-right: var(--tv-NavMenu-icon-margin-right);
+ font-size: var(--tv-NavMenu-icon-size);
+ fill: var(--tv-NavMenu-icon-color);
+ }
+ }
}
> span.selected,
> a.selected {
color: var(--tv-NavMenu-popmenu-selected-text-color);
+
+ svg {
+ fill: var(--tv-NavMenu-icon-color-hover);
+ }
}
&:only-child {
@@ -285,7 +334,18 @@
&:hover {
color: var(--tv-NavMenu-popmenu-text-color);
- text-decoration: underline;
+
+ > a:after,
+ > span:after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 1px;
+ background: var(--tv-NavMenu-item-selected-underline-bg-color);
+ z-index: 1;
+ }
}
&.active,
@@ -302,6 +362,18 @@
text-decoration: none;
font-size: var(--tv-NavMenu-popmenu-text-font-size);
cursor: pointer;
+ position: relative;
+
+ .menu-icon {
+ display: inline-block;
+
+ svg {
+ vertical-align: text-bottom;
+ margin-right: var(--tv-NavMenu-icon-margin-right);
+ font-size: var(--tv-NavMenu-icon-size);
+ fill: var(--tv-NavMenu-icon-color);
+ }
+ }
&.selected {
color: var(--tv-NavMenu-popmenu-selected-text-color);
@@ -317,15 +389,20 @@
background: var(--tv-NavMenu-item-selected-underline-bg-color);
z-index: 1;
}
- }
- &:hover {
- color: var(--tv-NavMenu-popmenu-text-color);
+ svg {
+ fill: var(--tv-NavMenu-icon-color-hover);
+ }
}
+ &:hover,
&.active,
&:active {
color: var(--tv-NavMenu-popmenu-text-color);
+
+ svg {
+ fill: var(--tv-NavMenu-icon-color-hover);
+ }
}
}
@@ -401,6 +478,10 @@
float: right;
margin-left: 10px;
}
+
+ svg {
+ vertical-align: text-bottom;
+ }
}
> .slot-mobile-menu {
diff --git a/packages/theme/src/nav-menu/vars.less b/packages/theme/src/nav-menu/vars.less
index b29d21bf97..256078e3f0 100644
--- a/packages/theme/src/nav-menu/vars.less
+++ b/packages/theme/src/nav-menu/vars.less
@@ -81,4 +81,12 @@
--tv-NavMenu-slot-logo-icon-color: var(--tv-color-icon-hover, #191919);
// 自定义 Logo大小
--tv-NavMenu-slot-logo-icon-size: var(--tv-icon-size, 16px);
+ // 导航菜单图标右侧边距
+ --tv-NavMenu-icon-margin-right: var(--tv-space-base);
+ // 导航菜单图标大小
+ --tv-NavMenu-icon-size: var(--tv-icon-size);
+ // 导航菜单图标色
+ --tv-NavMenu-icon-color: var(--tv-color-icon);
+ // 导航菜单图标悬浮色
+ --tv-NavMenu-icon-color-hover: var(--tv-color-icon-hover);
}
diff --git a/packages/vue/src/nav-menu/__tests__/nav-menu.test.tsx b/packages/vue/src/nav-menu/__tests__/nav-menu.test.tsx
index 9158c7eaf4..74e38f26b1 100644
--- a/packages/vue/src/nav-menu/__tests__/nav-menu.test.tsx
+++ b/packages/vue/src/nav-menu/__tests__/nav-menu.test.tsx
@@ -1,4 +1,4 @@
-import { IconTotal } from '@opentiny/vue-icon'
+import { IconTotal, IconShare } from '@opentiny/vue-icon'
import NavMenu from '@opentiny/vue-nav-menu'
import { describe, expect, test } from 'vitest'
import { mountPcMode } from '@opentiny-internal/vue-test-utils'
@@ -6,6 +6,9 @@ import { mountPcMode } from '@opentiny-internal/vue-test-utils'
describe('PC Mode', () => {
const mount = mountPcMode
+ const IconTotalComponent = IconTotal()
+ const IconShareComponent = IconShare()
+
const navMenuMockData = [
{
title: '首页',
@@ -200,8 +203,18 @@ describe('PC Mode', () => {
]
}
]
-
- const IconTotalComponent = IconTotal()
+ const navMenuMockDataWithIcon = [
+ {
+ title: '首页',
+ url: '',
+ icon: IconShareComponent
+ },
+ {
+ title: '导航',
+ url: '',
+ icon: ''
+ }
+ ]
/**
* attrs
@@ -213,6 +226,12 @@ describe('PC Mode', () => {
expect(navMenu.vm.state.data.length).toBe(3)
})
+ test('menu icon', async () => {
+ const wrapper = mount(() =>