Skip to content

Commit

Permalink
feat(components): 新增聚合svgIcon组件,同时支持本地图标和el-icon
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr.Mikey committed Jun 20, 2023
1 parent 0297379 commit 689f561
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 110 deletions.
1 change: 1 addition & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ declare module '@vue/runtime-core' {
RouterView: typeof import('vue-router')['RouterView']
VElementIcon: typeof import('./src/components/common/VElementIcon.vue')['default']
VLocalSvgIcon: typeof import('./src/components/common/VLocalSvgIcon.vue')['default']
VSvgIcon: typeof import('./src/components/common/VSvgIcon.vue')['default']
}
}
7 changes: 7 additions & 0 deletions docs/el-icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
如你不需要全局使用,请删除 `src/plugins/index.ts``elementPlusIconPlugin` 的相关配置,并将使用到的全局图标替换成按需使用方式或其它方式。


**也可以使用VSvgIcon组件,该组件聚合了element-plus icon和local svg,用法与VElementIcon一致,如:**

```html
<v-svg-icon name="i-ep-Location" />
```


## 按需

### 使用
Expand Down
6 changes: 6 additions & 0 deletions docs/local-svg.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
<v-local-svg-icon name="fullscreen" />
```

**也可以使用VSvgIcon组件,该组件聚合了element-plus icon和local svg,用法与VLocalSvgIcon稍有不同,需要在name上增加【local-】标识,如:**

```html
<v-svg-icon name="local-fullscreen" />
```

## 接入

安装依赖
Expand Down
34 changes: 15 additions & 19 deletions src/components/common/VLocalSvgIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,45 @@
* @Contact: 1303232158@qq.com
* @Date: 2022-07-14 00:31:13
* @LastEditors: Mr.Mikey
* @LastEditTime: 2023-06-19 18:09:50
* @LastEditTime: 2023-06-20 16:41:14
* @FilePath: \vue3-admin\src\components\common\VLocalSvgIcon.vue
-->
<template>
<div class="v-local-svg-icon">
<!-- https?:|mailto:|tel:渲染div -->
<div v-if="isExternals" :style="styleExternalIcon" class="local-svg-external-icon local-svg-icon" />
<!-- svg格式渲染svg标签 -->
<svg v-else :class="svgClass" aria-hidden="true">
<svg :style="setIconSvgStyle" aria-hidden="true" class="local-svg-icon">
<use :xlink:href="iconName" />
</svg>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { isExternal } from '@/utils/validate'

const props = defineProps({
// 对应assets/icons/svg目录下的文件名
name: {
type: String,
required: true,
},
className: {
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
default: '',
},
})

// 根据用户传入的iconClass判断文件扩展
const isExternals = computed(() => isExternal(props.name))
// 生成iconName
const iconName = computed(() => `#icon-${props.name}`)
const svgClass = computed(() => {
if (props.className) {
return 'local-svg-icon ' + props.className
} else {
return 'local-svg-icon'
}

// 设置图标样式
const setIconSvgStyle = computed(() => {
return `font-size: ${props.size}px;color: ${props.color};`
})
const styleExternalIcon = computed(() => ({
mask: `url(${props.name}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.name}) no-repeat 50% 50%`,
}))
</script>

<style lang="scss" scoped>
Expand Down
92 changes: 92 additions & 0 deletions src/components/common/VSvgIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!--
* @Description: 聚合svg组件,可使用本地图标、element-plus icon等
* @Tips: 亲,记得补全功能描述哦~ (ღ˘⌣˘ღ)
* @Author: Mr.Mikey
* @Contact: 1303232158@qq.com
* @Date: 2023-06-20 16:42:53
* @LastEditors: Mr.Mikey
* @LastEditTime: 2023-06-20 17:38:15
* @FilePath: \vue3-admin\src\components\common\VSvgIcon.vue
-->
<template>
<component :is="component.componentId" :name="component.name" v-bind="$attrs" />
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
// import VLocalSvgIcon from './VLocalSvgIcon.vue'
// import VElementIcon from './VElementIcon.vue'

const props = defineProps({
/**
* 示例:
* 本地图标请传name="local-文件名"
* element icon请传name="i-ep-icon名"
*
* 注:可查看docs目录下相关文件说明
*/
name: {
type: String,
required: true,
},
})

// 定义变量内容

// 组件配置
const components = {
// 本地图标
'local-': {
componentId: 'VLocalSvgIcon',
name: getLocalIconName(props.name),
},
// element-plus icons
'i-ep-': {
componentId: 'VElementIcon',
name: props.name,
},
// 扩展其他类型可在此处进行配置
}

// 当前组件
const component = ref({
componentId: 'VElementIcon', // 初始组件设为VElementIcon,
name: '', // 初始图标
})

/**
* 截取local svg name
* @param icon string routes配置中的meta.icon
*/
function getLocalIconName(icon: string) {
return icon.split('local-')[1]
}

/**
* 查找相应组件配置
* @param name iconName
*/
function findComponent(name: string) {
// 查找对应的配置进行赋值
Object.keys(components).forEach(key => {
if (name.startsWith(key)) {
component.value = components[key]
}
})
}

/**
* 监听props.name,根据配置渲染相应的组件
*/
watch(
() => props.name,
name => {
findComponent(name)
},
{
immediate: true,
}
)
</script>

<style scoped></style>
26 changes: 2 additions & 24 deletions src/layout/components/VAsideMenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
<!-- 有子菜单的 -->
<el-sub-menu v-if="item?.children?.length" :index="item.path">
<template #title>
<v-local-svg-icon v-if="checkIsLocalIcon(item.meta.icon)" :name="getLocalIcon(item.meta.icon)" />
<v-element-icon v-else :name="item.meta.icon" />
<v-svg-icon :name="item.meta.icon" />
<span>{{ item.meta.title }}</span>
</template>
<!-- 递归渲染子菜单 -->
Expand All @@ -14,8 +13,7 @@

<!-- 无子菜单的 -->
<el-menu-item v-else :index="item.path" :key="item.path">
<v-local-svg-icon v-if="checkIsLocalIcon(item.meta.icon)" :name="getLocalIcon(item.meta.icon)" />
<v-element-icon v-else :name="item.meta.icon" />
<v-svg-icon :name="item.meta.icon" />
<template #title>
<span>{{ item.meta.title }}</span>
</template>
Expand All @@ -32,26 +30,6 @@ const props = defineProps({
default: () => ({}),
},
})

/**
* 截取local svg name
* @param icon string routes配置中的meta.icon
*/
const getLocalIcon = (icon: string) => {
return icon.split(' ')[1]
}

/**
* 检查是否本地icon
* @param icon string routes配置中的meta.icon
*/
const checkIsLocalIcon = (icon: string) => {
if (icon.startsWith('local')) {
return true
}

return false
}
</script>

<style scoped></style>
75 changes: 11 additions & 64 deletions src/layout/components/VHeaderTags.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div class="tabs-container">
<div class="layout-navbar-tags">
<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll"></el-scrollbar>
<!-- <el-tabs v-model="editableTabsValue" type="card" editable class="demo-tabs" @edit="handleTabsEdit">
<el-tab-pane v-for="item in editableTabs" :key="item.name" :label="item.title" :name="item.name">
{{ item.content }}
Expand All @@ -8,69 +9,15 @@
</div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
<script lang="ts" setup>
import { ref } from 'vue'
export default defineComponent({
setup() {
let tabIndex = 2
const editableTabsValue = ref('2')
const editableTabs = ref([
{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content',
},
{
title: 'Tab 2',
name: '2',
content: 'Tab 2 content',
},
])
// 定义变量内容
const scrollbarRef = ref()
const tagList = ref([])
const handleTabsEdit = (targetName: string, action: 'remove' | 'add') => {
if (action === 'add') {
const newTabName = `${++tabIndex}`
editableTabs.value.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content',
})
editableTabsValue.value = newTabName
} else if (action === 'remove') {
const tabs = editableTabs.value
let activeName = editableTabsValue.value
if (activeName === targetName) {
tabs.forEach((tab: any, index: number) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
editableTabsValue.value = activeName
editableTabs.value = tabs.filter((tab: any) => tab.name !== targetName)
}
}
return {
editableTabsValue,
editableTabs,
handleTabsEdit,
}
},
})
</script>

<style scoped lang="scss">
.tabs-container {
display: flex;
align-items: center;
height: 30px;
padding: 0 20px;
border-bottom: 1px solid #f1f1f1;
// 鼠标滚轮滚动
const onHandleScroll = (e: WheelEventType) => {
scrollbarRef.value.$refs.wrapRef.scrollLeft += e.wheelDelta / 4
}
</style>
</script>
12 changes: 10 additions & 2 deletions src/layout/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
// pc端左侧导航折叠样式
.layout-aside-pc-64 {
width: 64px;
transition: 0.3s ease-in;
transition: 0.3s ease-out;
}

.layout-logo {
Expand All @@ -115,9 +115,10 @@
height: 100px;
font-size: 20px;
font-weight: bold;
border-bottom: 1px solid #e9e9e9;
border-bottom: 1px solid var(--next-border-color-light);
}

// 导航容器
.layout-nav-container {
display: flex;
align-items: center;
Expand All @@ -135,8 +136,15 @@
}
}

// 面包屑
.layout-navbars-breadcrumb {
padding-top: 2px;
}

// tags
.layout-navbar-tags {
height: 30px;
border-bottom: 1px solid var(--next-border-color-light);
}
}
}
2 changes: 1 addition & 1 deletion src/router/routerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Error from '@/views/error/routes'
* isKeepAlive: 是否缓存组件状态
* isAffix: 是否固定在 tagsView 栏上
* isIframe: 是否内嵌窗口,开启条件,`1、isIframe:true 2、isLink:链接地址不为空`
* icon: 菜单、tagsView 图标
* icon: 菜单、tagsView 图标,支持本地图标和element-plus icon,本地图标配置前缀为local-文件名,el-icon配置前缀为i-ep-图标名
* }
*/

Expand Down
5 changes: 5 additions & 0 deletions src/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ declare global {
// 添加一个全局loading效果
nextLoading: boolean
}

// 鼠标滚轮滚动类型
declare interface WheelEventType extends WheelEvent {
wheelDelta: number
}
}

0 comments on commit 689f561

Please sign in to comment.