
12 changed files with 1137 additions and 193 deletions
File diff suppressed because it is too large
@ -0,0 +1,7 @@ |
|||||
|
import Breadcrumb from './src/index.vue' |
||||
|
|
||||
|
Breadcrumb.install = function(Vue) { |
||||
|
Vue.component(Breadcrumb.name || 'Breadcrumb', Breadcrumb) |
||||
|
} |
||||
|
|
||||
|
export default Breadcrumb |
@ -0,0 +1,160 @@ |
|||||
|
<template> |
||||
|
<div v-if="breadcrumbs.length > 0" class="breadcrumb-container flex-between"> |
||||
|
<nav> |
||||
|
<ol class="breadcrumb"> |
||||
|
<li |
||||
|
v-for="(item, index) in breadcrumbs" |
||||
|
:key="index" |
||||
|
class="breadcrumb-item" |
||||
|
:class="{ active: index === breadcrumbs.length - 1 }" |
||||
|
> |
||||
|
<template v-if="index !== breadcrumbs.length - 1"> |
||||
|
<router-link to="/" v-if="item.title == '首页'"> |
||||
|
<SvgIcon1 :iconPath="require(`../../assets/home-bread.svg`)" defaultColor="#8A9099" :size="16" activeColor="#006AFF"/> |
||||
|
</router-link> |
||||
|
<router-link v-else :to="item.path">{{ item.title }}</router-link> |
||||
|
<img class="separator" src="../../assets/separator.png" alt=""> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<span>{{ item.title }}</span> |
||||
|
</template> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</nav> |
||||
|
<div v-if="breadRightText" class="gap8 breadRight"> |
||||
|
<img class="ml-8" src="../../assets/bind_sites.svg" alt="" /> |
||||
|
站点简称:<a :href="breadRightText">{{ breadRightText }}</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script> |
||||
|
import SvgIcon1 from '../..//SvgIcon1'; |
||||
|
export default { |
||||
|
name: 'Breadcrumb', |
||||
|
components: { |
||||
|
SvgIcon1, |
||||
|
}, |
||||
|
computed: { |
||||
|
breadcrumbs() { |
||||
|
if (this.$route.meta.hideBreadcrumb) return []; |
||||
|
|
||||
|
const crumbs = []; |
||||
|
let currentRoute = this.$route; |
||||
|
|
||||
|
// 递归查找所有父级路由 |
||||
|
while (currentRoute) { |
||||
|
// 获取匹配的路由记录 |
||||
|
// const matchedRoute = this.$router.options.routes.find( |
||||
|
// r => r.name === currentRoute.name |
||||
|
// ); |
||||
|
|
||||
|
// 构建包含完整参数的对象 |
||||
|
const routeWithParams = { |
||||
|
path: currentRoute.path, |
||||
|
query: currentRoute.query, |
||||
|
params: currentRoute.params |
||||
|
}; |
||||
|
|
||||
|
crumbs.unshift({ |
||||
|
path: routeWithParams, |
||||
|
title: this.getTitle(currentRoute) |
||||
|
}); |
||||
|
|
||||
|
// 通过 meta.breadcrumbParent 查找父级路由 |
||||
|
if (currentRoute.meta.breadcrumbParent) { |
||||
|
currentRoute = this.$router.options.routes.find( |
||||
|
r => r.name === currentRoute.meta.breadcrumbParent |
||||
|
); |
||||
|
|
||||
|
// 如果找到了父路由,创建一个模拟的$route对象 |
||||
|
if (currentRoute) { |
||||
|
currentRoute = { |
||||
|
...currentRoute, |
||||
|
path: currentRoute.path, |
||||
|
query: this.$route.query, // 保留当前查询参数 |
||||
|
params: this.$route.params, // 保留当前路由参数 |
||||
|
meta: currentRoute.meta || {} |
||||
|
}; |
||||
|
} |
||||
|
} else { |
||||
|
currentRoute = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return crumbs; |
||||
|
}, |
||||
|
breadRightText() { |
||||
|
return this.$store && this.$store.state.breadRightText |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
getTitle(route) { |
||||
|
return typeof route.meta.title === 'function' |
||||
|
? route.meta.title(route) |
||||
|
: route.meta.title || route.name; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
<style scoped lang="scss"> |
||||
|
/* 保持之前的样式不变 */ |
||||
|
.breadcrumb-container { |
||||
|
padding: 16px 12px; |
||||
|
background-color: #f5f5f5; |
||||
|
border-radius: 4px; |
||||
|
.breadRight{ |
||||
|
a{ |
||||
|
text-decoration: none; |
||||
|
color: #006AFF; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.home-icon { |
||||
|
width: 16px; |
||||
|
height: 16px; |
||||
|
} |
||||
|
|
||||
|
.breadcrumb { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
height: 22px; |
||||
|
align-items: center; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
list-style: none; |
||||
|
} |
||||
|
|
||||
|
.breadcrumb-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.router-link-active { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.breadcrumb-item a { |
||||
|
color: #626573; |
||||
|
text-decoration: none; |
||||
|
|
||||
|
&:hover { |
||||
|
color: #006AFF; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.breadcrumb-item.active span { |
||||
|
color: #1E2226; |
||||
|
; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.separator { |
||||
|
width: 12px; |
||||
|
height: 12px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,7 @@ |
|||||
|
import SvgIcon1 from './src/index.vue' |
||||
|
|
||||
|
SvgIcon1.install = function(Vue) { |
||||
|
Vue.component(SvgIcon1.name || 'SvgIcon1', SvgIcon1) |
||||
|
} |
||||
|
|
||||
|
export default SvgIcon1 |
@ -0,0 +1,175 @@ |
|||||
|
<template> |
||||
|
<div class="svg-icon-wrapper" :style="wrapperStyle" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> |
||||
|
<div class="svg-icon" v-html="svgContent" :style="{ |
||||
|
'--icon-color': (hoverEffect || isActive) ? activeColor : defaultColor, |
||||
|
'--icon-hover-color': activeColor |
||||
|
}"></div> |
||||
|
<!-- :style="iconStyle" --> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'SvgIcon1', |
||||
|
props: { |
||||
|
// 图标路径(必须) |
||||
|
iconPath: { |
||||
|
type: String, |
||||
|
required: true |
||||
|
}, |
||||
|
// 默认颜色 |
||||
|
defaultColor: { |
||||
|
type: String, |
||||
|
default: '#606266' |
||||
|
}, |
||||
|
// 激活颜色(传入后才允许变色) |
||||
|
activeColor: { |
||||
|
type: String, |
||||
|
default: null |
||||
|
}, |
||||
|
// 是否开启悬停变色 |
||||
|
hoverEffect: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
// 图标大小 |
||||
|
size: { |
||||
|
type: [String, Number], |
||||
|
default: '14px' |
||||
|
}, |
||||
|
// 是否当前激活状态 |
||||
|
isActive: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
svgContent: '', |
||||
|
isHovering: false, |
||||
|
currentColor: this.defaultColor |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
wrapperStyle() { |
||||
|
return { |
||||
|
width: typeof this.size === 'number' ? `${this.size}px` : this.size, |
||||
|
height: typeof this.size === 'number' ? `${this.size}px` : this.size, |
||||
|
display: 'inline-flex', |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'center' |
||||
|
} |
||||
|
}, |
||||
|
iconStyle() { |
||||
|
return { |
||||
|
width: '100%', |
||||
|
height: '100%', |
||||
|
color: this.currentColor, |
||||
|
transition: 'color 0.3s ease' |
||||
|
} |
||||
|
}, |
||||
|
shouldChangeColor() { |
||||
|
return this.activeColor && (this.hoverEffect || this.isActive) |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
isActive(newVal) { |
||||
|
console.log(newVal, 'newVal---'); |
||||
|
if (this.shouldChangeColor) { |
||||
|
this.currentColor = newVal ? this.activeColor : this.defaultColor |
||||
|
console.log(this.currentColor, 'this.currentColor--'); |
||||
|
} |
||||
|
}, |
||||
|
defaultColor(newVal) { |
||||
|
if (!this.isHovering && !this.isActive) { |
||||
|
this.currentColor = newVal |
||||
|
} |
||||
|
}, |
||||
|
activeColor() { |
||||
|
this.updateColorState() |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
async loadSvg() { |
||||
|
try { |
||||
|
const response = await fetch(this.iconPath) |
||||
|
this.svgContent = await response.text() |
||||
|
this.processSvg() |
||||
|
} catch (error) { |
||||
|
console.error('Failed to load SVG:', error) |
||||
|
} |
||||
|
}, |
||||
|
processSvg() { |
||||
|
// 确保SVG没有自带颜色,以便用CSS控制 |
||||
|
this.$nextTick(() => { |
||||
|
const svgElement = this.$el.querySelector('svg') |
||||
|
if (svgElement) { |
||||
|
// 更彻底地移除颜色属性 |
||||
|
svgElement.removeAttribute('fill') |
||||
|
svgElement.removeAttribute('style') |
||||
|
const paths = svgElement.querySelectorAll('path, circle, rect, polygon') |
||||
|
paths.forEach(el => { |
||||
|
el.removeAttribute('fill') |
||||
|
}) |
||||
|
svgElement.style.fill = 'currentColor' |
||||
|
svgElement.style.width = '100%' |
||||
|
svgElement.style.height = '100%' |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
handleMouseEnter() { |
||||
|
this.isHovering = true |
||||
|
this.updateColorState() |
||||
|
}, |
||||
|
handleMouseLeave() { |
||||
|
this.isHovering = false |
||||
|
this.updateColorState() |
||||
|
}, |
||||
|
updateColorState() { |
||||
|
if (this.activeColor) { |
||||
|
if (this.isActive) { |
||||
|
this.currentColor = this.activeColor |
||||
|
} else { |
||||
|
this.currentColor = this.isHovering && this.hoverEffect ? |
||||
|
this.activeColor : |
||||
|
this.defaultColor |
||||
|
} |
||||
|
} else { |
||||
|
this.currentColor = this.defaultColor |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
this.loadSvg() |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.svg-icon-wrapper { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.svg-icon { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
color: var(--icon-color); |
||||
|
transition: color 0.3s; |
||||
|
} |
||||
|
|
||||
|
.svg-icon:hover { |
||||
|
color: var(--icon-hover-color); |
||||
|
} |
||||
|
|
||||
|
.svg-icon { |
||||
|
display: inline-flex; |
||||
|
} |
||||
|
|
||||
|
.svg-icon>>>svg { |
||||
|
fill: currentColor; |
||||
|
} |
||||
|
</style> |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 662 B |
@ -0,0 +1,5 @@ |
|||||
|
module.exports = { |
||||
|
plugins: [ |
||||
|
require('autoprefixer')({ grid: true }) |
||||
|
] |
||||
|
} |
Loading…
Reference in new issue