feat: 新增自定义图标库并调整菜单图标与菜单归属

1. 新增 iconfont 自定义图标库及相关依赖文件
2. 调整菜单图标为自定义字体图标,替换原有 Ant Design 图标
3. 将客户拜访菜单从客户管理移动到项目管理作为首个子菜单
4. 优化侧边菜单展开逻辑与图标样式适配
This commit is contained in:
andyesfly committed 2026-09-11 17:01:24 +08:00
1 parent c60caa4770
commit 68af363ca9
12 files changed
+207 -19

No files matched your search

+52 -7
View File
@@ -140,7 +140,14 @@ const iconMap: Record<string, Component> = {
DashboardOutlined,
}
// iconfont 图标类名前缀(对应 assets/fonts/iconfont/iconfont.css 的 css_prefix_text)
const ICONFONT_PREFIX = 'xt-'
function resolveIcon(icon?: string | null) {
// icon 值形如 xt-xxx 时渲染 iconfont 图标,否则回退到 ant-design 图标
if (icon && icon.startsWith(ICONFONT_PREFIX)) {
return () => h('i', { class: `iconfont ${icon}` })
}
const iconComponent = icon ? iconMap[icon] : undefined
return () => h(iconComponent || MenuOutlined)
}
@@ -194,6 +201,20 @@ const menuItems = computed<MenuItem[]>(() => [
...buildMenuItems(permissionStore.menus),
])
// 子菜单路径 → 所属父级目录的菜单 key,用于按当前路由展开对应的父菜单
// (不能按路径首段推导:如“客户拜访”归属项目管理,但路径仍为 /customer/visit)
const parentKeyMap = computed<Record<string, string>>(() => {
const map: Record<string, string> = {}
for (const node of permissionStore.menus) {
if (node.menuType !== 1) continue
const dirKey = node.menuPath || `dir-${node.id}`
for (const child of node.children || []) {
if (child.menuPath) map[child.menuPath] = dirKey
}
}
return map
})
// 计算属性
const userInfo = computed(() => permissionStore.userInfo)
const displayName = computed(() => userInfo.value?.realName || userInfo.value?.username || '管理员')
@@ -219,10 +240,10 @@ watch(
(path) => {
selectedKeys.value = [path]
// 自动展开父级菜单
const pathParts = path.split('/').filter(Boolean)
if (pathParts.length > 1) {
openKeys.value = [`/${pathParts[0]}`]
// 自动展开当前菜单所属的父级目录
const parentKey = parentKeyMap.value[path]
if (parentKey) {
openKeys.value = [parentKey]
}
},
{ immediate: true }
@@ -234,9 +255,9 @@ watch(collapsed, (val) => {
openKeys.value = []
} else {
// 展开时根据当前路由自动打开对应的父菜单
const pathParts = route.path.split('/').filter(Boolean)
if (pathParts.length > 1) {
openKeys.value = [`/${pathParts[0]}`]
const parentKey = parentKeyMap.value[route.path]
if (parentKey) {
openKeys.value = [parentKey]
}
}
})
@@ -306,6 +327,30 @@ function handleUserMenuClick({ key }: { key: string }) {
border-right: none;
}
// iconfont 图标统一:该图标集为彩色字体,颜色写死在字形里,无法用 color 覆盖,
// 这里用 filter 统一压成单色,并按菜单状态在灰 / 白之间切换,与 ant-design 图标观感保持一致
// 尺寸统一:ant-design 菜单图标为 14px,而 iconfont.css 自带 font-size: 16px,
// 故此处显式对齐为 14px,并重置 line-height,避免字体图标在 antd 的 line-height: 0 下出现基线偏移
:deep(.ant-menu) .iconfont {
display: inline-block;
font-size: 14px;
line-height: 1;
vertical-align: -0.125em;
filter: brightness(0) invert(0.65);
}
// 折叠态与 ant-design 图标同步放大到 16px
:deep(.ant-menu-inline-collapsed) .iconfont {
font-size: 16px;
}
:deep(.ant-menu-item:hover) .iconfont,
:deep(.ant-menu-item-selected) .iconfont,
:deep(.ant-menu-submenu-title:hover) .iconfont,
:deep(.ant-menu-submenu-selected) .iconfont {
filter: brightness(0) invert(1);
}
.collapse-btn {
position: absolute;
bottom: 0;