u
This commit is contained in:
1 parent
32d423b759
commit
78eb811ff7
102 files changed
+12067
-209
No files matched your search
@@ -3,31 +3,59 @@ import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { House } from '@lucide/vue'
|
||||
import { usePreferencesStore } from '@/preferences'
|
||||
import { findMenuItemByPath, findParentKeyByPath } from '@/router'
|
||||
import { menuItems } from '@/constants/menu'
|
||||
import { findMenuItemByKey, findMenuItemByPath, findParentKeysByPath } from '@/router'
|
||||
|
||||
/** 面包屑单项:有 path 的是可点的父级,没有的是当前页。 */
|
||||
interface Crumb {
|
||||
title: string
|
||||
path?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 面包屑。
|
||||
* 数据来自菜单常量:父级分组 + 当前页,与侧栏同源,不额外维护。
|
||||
* 数据来自菜单常量:祖先分组链 + 当前页,与侧栏同源,不额外维护。
|
||||
*/
|
||||
const route = useRoute()
|
||||
const prefStore = usePreferencesStore()
|
||||
const pref = computed(() => prefStore.preferences)
|
||||
|
||||
/** 当前路径对应的祖先链:[分组, 当前页] */
|
||||
const crumbs = computed(() => {
|
||||
const list: Array<{ title: string; path?: string }> = []
|
||||
/**
|
||||
* 当前路径对应的祖先链:[...分组, 当前页]。
|
||||
*
|
||||
* 父级用 findParentKeysByPath(返回【完整】祖先 key 链)而不是
|
||||
* findParentKeyByPath(只取 [0] = 顶级)—— 后者在多级菜单下会漏掉中间层:
|
||||
* 「海运 → 订舱业务 → 订舱单」只会显示成「海运 / 订舱单」。
|
||||
*/
|
||||
const crumbs = computed<Crumb[]>(() => {
|
||||
const list: Crumb[] = []
|
||||
|
||||
if (pref.value.breadcrumb.showHome) {
|
||||
list.push({ title: '首页', path: '/' })
|
||||
}
|
||||
|
||||
const current = findMenuItemByPath(route.path)
|
||||
const parentKey = findParentKeyByPath(route.path)
|
||||
const parent = parentKey ? menuItems.find((item) => item.key === parentKey) : undefined
|
||||
// 1) 菜单里的完整祖先链(顶级 → 最近一级分组)
|
||||
for (const key of findParentKeysByPath(route.path)) {
|
||||
const node = findMenuItemByKey(key)
|
||||
if (node) list.push({ title: node.title, path: node.path })
|
||||
}
|
||||
|
||||
if (parent) list.push({ title: parent.title, path: parent.path })
|
||||
if (current && current.key !== parent?.key) list.push({ title: current.title })
|
||||
const current = findMenuItemByPath(route.path)
|
||||
|
||||
// 2) 菜单里查不到祖先(页面不在菜单树 / 菜单与路由不同步)时,
|
||||
// 用路由层级兜底:route.matched 上带 title 的祖先记录即父级。
|
||||
// 当前页不在这里补(由下面统一追加),避免自链接。
|
||||
if (!current && list.length === 0) {
|
||||
for (const record of route.matched) {
|
||||
if (record.path === route.path) break
|
||||
const title = record.meta.title as string | undefined
|
||||
if (title) list.push({ title, path: record.path })
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 当前页:菜单里查得到就用菜单名(业务页);
|
||||
// 查不到(403/404/500 等缺省页、详情页)回退到 route.meta.title。
|
||||
const currentTitle = current?.title ?? (route.meta.title as string | undefined)
|
||||
if (currentTitle) list.push({ title: currentTitle })
|
||||
|
||||
return list
|
||||
})
|
||||
@@ -50,11 +78,16 @@ const visible = computed(() => {
|
||||
<span v-if="index > 0" class="layout-breadcrumb__sep">/</span>
|
||||
|
||||
<RouterLink v-if="crumb.path" class="layout-breadcrumb__item" :to="crumb.path">
|
||||
<House v-if="index === 0 && pref.breadcrumb.showHome" :size="14" />
|
||||
<span>{{ crumb.title }}</span>
|
||||
<!-- __content 是 background 风格里做「内容整体左移」的包裹层(照 vben 的 li a > span) -->
|
||||
<span class="layout-breadcrumb__content">
|
||||
<House v-if="index === 0 && pref.breadcrumb.showHome" :size="14" />
|
||||
{{ crumb.title }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
|
||||
<span v-else class="layout-breadcrumb__item is-current">{{ crumb.title }}</span>
|
||||
<span v-else class="layout-breadcrumb__item is-current">
|
||||
<span class="layout-breadcrumb__content">{{ crumb.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -65,13 +98,8 @@ const visible = computed(() => {
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
|
||||
&.is-background {
|
||||
padding: 4px 10px;
|
||||
background: var(--g3-color-fill-hover, #f2f3f5);
|
||||
border-radius: 6px;
|
||||
}
|
||||
/* 字号:15px —— 比顶栏 18px 的图标按钮小一档,层次清楚又不至于太弱 */
|
||||
font-size: 15px;
|
||||
|
||||
&__sep {
|
||||
color: var(--g3-color-text-tertiary, #86909c);
|
||||
@@ -80,7 +108,6 @@ const visible = computed(() => {
|
||||
&__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--g3-color-text-secondary, #4e5969);
|
||||
text-decoration: none;
|
||||
|
||||
@@ -92,5 +119,113 @@ const visible = computed(() => {
|
||||
color: var(--g3-color-text, #1f2329);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* ---- background 风格 ----
|
||||
* 照搬 vben 的 breadcrumb-background.vue:整条不是「灰底圆角块」,而是
|
||||
* 一段段 accent 底色的「>」形(面包屑步骤条)。段与段之间不再用 / 分隔符,
|
||||
* 靠两块 14px 边框三角拼接:
|
||||
* ::before 放在段左侧 28px 处,左三角透明 → 形成向左的凹口;
|
||||
* ::after 放在段右侧,只留左三角着色 → 形成指向右的尖角。
|
||||
* 14px 边框 × 上下两半 = 28px,正好等于段高 h-7(28px)。 */
|
||||
&.is-background {
|
||||
gap: 0;
|
||||
font-size: 13px;
|
||||
|
||||
/* 有箭头就没有分隔符了 */
|
||||
.layout-breadcrumb__sep {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-breadcrumb__item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
/* mr-9(36px):给右箭头占的 28px 留位,另留 8px 视觉间隙 */
|
||||
margin-right: 36px;
|
||||
padding: 0 8px 0 5px;
|
||||
color: var(--g3-color-text-secondary, #4e5969);
|
||||
background: var(--g3-color-accent, #f2f3f5);
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 14px solid var(--g3-color-accent, #f2f3f5);
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: -28px;
|
||||
z-index: 10;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: 100%;
|
||||
border-color: transparent;
|
||||
border-left-color: var(--g3-color-accent, #f2f3f5);
|
||||
}
|
||||
|
||||
/* 首段:左端不开口,左侧留白加大并做左圆角 */
|
||||
&:first-child {
|
||||
padding-left: 15px;
|
||||
border-radius: 2px 0 0 2px;
|
||||
|
||||
&::before {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 末段(当前页):不做右箭头,右侧留白加大并做右圆角 */
|
||||
&:last-child {
|
||||
padding-right: 15px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-current {
|
||||
color: var(--g3-color-foreground, #373c43);
|
||||
}
|
||||
|
||||
/* 悬停只换底色,不改文字色(对齐 vben:当前页不可点,不参与 hover) */
|
||||
&:not(:last-child):hover {
|
||||
color: var(--g3-color-text-secondary, #4e5969);
|
||||
background: var(--g3-color-accent-hover, #e3e5e8);
|
||||
|
||||
&::before {
|
||||
border-color: var(--g3-color-accent-hover, #e3e5e8);
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-left-color: var(--g3-color-accent-hover, #e3e5e8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容整体左移,视觉上落进「>」的口子里(照 vben 的 -ml-3 / 首段 -ml-1) */
|
||||
.layout-breadcrumb__content {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.layout-breadcrumb__item:first-child .layout-breadcrumb__content {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user