refactor: custom icon

This commit is contained in:
张传龙 2022-08-27 11:46:34 +08:00
parent 2f1b747243
commit 0cefadc2a5
15 changed files with 1024 additions and 12 deletions

View File

@ -10,10 +10,11 @@ import IconsResolver from 'unplugin-icons/resolver'
* 图标库: https://icones.js.org/ * 图标库: https://icones.js.org/
*/ */
import Icons from 'unplugin-icons/vite' import Icons from 'unplugin-icons/vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { getRootPath } from '../utils' import { getRootPath } from '../utils'
const customIconPath = getRootPath('src', 'assets/icons') const customIconPath = getRootPath('src', 'assets/svg')
export default [ export default [
AutoImport({ AutoImport({
imports: ['vue', 'vue-router'], imports: ['vue', 'vue-router'],
@ -31,4 +32,10 @@ export default [
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })], resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })],
dts: false, dts: false,
}), }),
createSvgIconsPlugin({
iconDirs: [customIconPath],
symbolId: 'icon-custom-[dir]-[name]',
inject: 'body-last',
customDomId: '__CUSTOM_SVG_ICON__',
}),
] ]

View File

@ -55,6 +55,7 @@
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0", "vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-setup-extend-plus": "^0.1.0" "vite-plugin-vue-setup-extend-plus": "^0.1.0"
}, },
"config": { "config": {

933
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448 512" data-v-fba6e5d0=""><path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-92.2 312.9c-63.4 0-85.4-28.6-97.1-64.1c-16.3-51-21.5-84.3-63-84.3c-22.4 0-45.1 16.1-45.1 61.2c0 35.2 18 57.2 43.3 57.2c28.6 0 47.6-21.3 47.6-21.3l11.7 31.9s-19.8 19.4-61.2 19.4c-51.3 0-79.9-30.1-79.9-85.8c0-57.9 28.6-92 82.5-92c73.5 0 80.8 41.4 100.8 101.9c8.8 26.8 24.2 46.2 61.2 46.2c24.9 0 38.1-5.5 38.1-19.1c0-19.9-21.8-22-49.9-28.6c-30.4-7.3-42.5-23.1-42.5-48c0-40 32.3-52.4 65.2-52.4c37.4 0 60.1 13.6 63 46.6l-36.7 4.4c-1.5-15.8-11-22.4-28.6-22.4c-16.1 0-26 7.3-26 19.8c0 11 4.8 17.6 20.9 21.3c32.7 7.1 71.8 12 71.8 57.5c.1 36.7-30.7 50.6-76.1 50.6z" fill="#316c72"></path></svg>

Before

Width:  |  Height:  |  Size: 825 B

1
src/assets/svg/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448 512"><path fill="currentColor" d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-92.2 312.9c-63.4 0-85.4-28.6-97.1-64.1c-16.3-51-21.5-84.3-63-84.3c-22.4 0-45.1 16.1-45.1 61.2c0 35.2 18 57.2 43.3 57.2c28.6 0 47.6-21.3 47.6-21.3l11.7 31.9s-19.8 19.4-61.2 19.4c-51.3 0-79.9-30.1-79.9-85.8c0-57.9 28.6-92 82.5-92c73.5 0 80.8 41.4 100.8 101.9c8.8 26.8 24.2 46.2 61.2 46.2c24.9 0 38.1-5.5 38.1-19.1c0-19.9-21.8-22-49.9-28.6c-30.4-7.3-42.5-23.1-42.5-48c0-40 32.3-52.4 65.2-52.4c37.4 0 60.1 13.6 63 46.6l-36.7 4.4c-1.5-15.8-11-22.4-28.6-22.4c-16.1 0-26 7.3-26 19.8c0 11 4.8 17.6 20.9 21.3c32.7 7.1 71.8 12 71.8 57.5c.1 36.7-30.7 50.6-76.1 50.6z"></path></svg>

After

Width:  |  Height:  |  Size: 811 B

View File

@ -0,0 +1,25 @@
<script setup>
import { renderCustomIcon } from '@/utils/icon'
const props = defineProps({
/** 图标名称(图片的文件名) */
icon: {
type: String,
required: true,
},
size: {
type: Number,
default: 14,
},
color: {
type: String,
default: undefined,
},
})
const iconCom = computed(() => renderCustomIcon(props.icon, props))
</script>
<template>
<component :is="iconCom" />
</template>

View File

@ -0,0 +1,24 @@
<script setup name="SvgIcon">
const props = defineProps({
icon: {
type: String,
required: true,
},
prefix: {
type: String,
default: 'icon-custom',
},
color: {
type: String,
default: 'currentColor',
},
})
const symbolId = computed(() => `#${props.prefix}-${props.icon}`)
</script>
<template>
<svg aria-hidden="true" width="1em" height="1em">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>

View File

@ -5,14 +5,14 @@
:key="item.path" :key="item.path"
@click="handleBreadClick(item.path)" @click="handleBreadClick(item.path)"
> >
<component :is="renderIcon(item.meta?.icon, { size: 16 })" v-if="item.meta?.icon" /> <component :is="getIcon(item.meta)" />
{{ item.meta.title }} {{ item.meta.title }}
</n-breadcrumb-item> </n-breadcrumb-item>
</n-breadcrumb> </n-breadcrumb>
</template> </template>
<script setup> <script setup>
import { renderIcon } from '@/utils/icon' import { renderCustomIcon, renderIcon } from '@/utils/icon'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -21,4 +21,10 @@ function handleBreadClick(path) {
if (path === route.path) return if (path === route.path) return
router.push(path) router.push(path)
} }
function getIcon(meta) {
if (meta?.customIcon) return renderCustomIcon(meta.customIcon, { size: 18 })
if (meta?.icon) return renderIcon(meta.icon, { size: 18 })
return null
}
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<router-link h-60 f-c-c to="/"> <router-link h-60 f-c-c to="/">
<icon-custom-logo text-36></icon-custom-logo> <icon-custom-logo text-36 color-primary></icon-custom-logo>
<h2 v-show="!appStore.collapsed" ml-10 color-primary text-16 font-bold max-w-140 flex-shrink-0> <h2 v-show="!appStore.collapsed" ml-10 color-primary text-16 font-bold max-w-140 flex-shrink-0>
{{ title }} {{ title }}
</h2> </h2>

View File

@ -16,7 +16,7 @@ import { usePermissionStore } from '@/store/modules/permission'
import { isExternal } from '@/utils/is' import { isExternal } from '@/utils/is'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { renderIcon } from '@/utils/icon' import { renderCustomIcon, renderIcon } from '@/utils/icon'
const router = useRouter() const router = useRouter()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
@ -43,7 +43,7 @@ function getMenuItem(route, basePath = '') {
label: (route.meta && route.meta.title) || route.name, label: (route.meta && route.meta.title) || route.name,
key: route.name, key: route.name,
path: resolvePath(basePath, route.path), path: resolvePath(basePath, route.path),
icon: route.meta?.icon ? renderIcon(route.meta?.icon, { size: 16 }) : renderIcon('mdi:circle-outline', { size: 8 }), icon: getIcon(route.meta),
order: route.meta?.order || 0, order: route.meta?.order || 0,
} }
@ -58,9 +58,7 @@ function getMenuItem(route, basePath = '') {
label: singleRoute.meta?.title || singleRoute.name, label: singleRoute.meta?.title || singleRoute.name,
key: singleRoute.name, key: singleRoute.name,
path: resolvePath(menuItem.path, singleRoute.path), path: resolvePath(menuItem.path, singleRoute.path),
icon: singleRoute.meta?.icon icon: getIcon(singleRoute.meta),
? renderIcon(singleRoute.meta?.icon, { size: 16 })
: renderIcon('mdi:circle-outline', { size: 8 }),
order: menuItem.order, order: menuItem.order,
} }
const visibleItems = singleRoute.children ? singleRoute.children.filter((item) => item.name && !item.isHidden) : [] const visibleItems = singleRoute.children ? singleRoute.children.filter((item) => item.name && !item.isHidden) : []
@ -79,6 +77,12 @@ function getMenuItem(route, basePath = '') {
return menuItem return menuItem
} }
function getIcon(meta) {
if (meta?.customIcon) return renderCustomIcon(meta.customIcon, { size: 18 })
if (meta?.icon) return renderIcon(meta.icon, { size: 18 })
return null
}
function handleMenuSelect(key, item) { function handleMenuSelect(key, item) {
if (isExternal(item.path)) { if (isExternal(item.path)) {
window.open(item.path) window.open(item.path)

View File

@ -2,6 +2,7 @@ import '@/styles/reset.css'
import '@/styles/variables.css' import '@/styles/variables.css'
import '@/styles/index.scss' import '@/styles/index.scss'
import 'uno.css' import 'uno.css'
import 'virtual:svg-icons-register'
import { createApp } from 'vue' import { createApp } from 'vue'
import { setupRouter } from '@/router' import { setupRouter } from '@/router'

View File

@ -1,7 +1,12 @@
import { h } from 'vue' import { h } from 'vue'
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import { NIcon } from 'naive-ui' import { NIcon } from 'naive-ui'
import SvgIcon from '@/components/custom/SvgIcon.vue'
export function renderIcon(icon, props = { size: 12 }) { export function renderIcon(icon, props = { size: 12 }) {
return () => h(NIcon, props, { default: () => h(Icon, { icon }) }) return () => h(NIcon, props, { default: () => h(Icon, { icon }) })
} }
export function renderCustomIcon(icon, props = { size: 12 }) {
return () => h(NIcon, props, { default: () => h(SvgIcon, { icon }) })
}

View File

@ -17,7 +17,7 @@ export default {
component: () => import('./404.vue'), component: () => import('./404.vue'),
meta: { meta: {
title: '404', title: '404',
icon: 'mdi:alert-circle-outline', icon: 'tabler:error-404',
}, },
}, },
], ],

View File

@ -31,6 +31,7 @@ export default {
component: () => import('./post/index.vue'), component: () => import('./post/index.vue'),
meta: { meta: {
title: '文章列表', title: '文章列表',
icon: 'material-symbols:auto-awesome-outline-rounded',
role: ['admin'], role: ['admin'],
requireAuth: true, requireAuth: true,
}, },
@ -41,6 +42,7 @@ export default {
component: () => import('./post/PostCreate.vue'), component: () => import('./post/PostCreate.vue'),
meta: { meta: {
title: '创建文章', title: '创建文章',
icon: 'material-symbols:auto-awesome-outline-rounded',
role: ['admin'], role: ['admin'],
requireAuth: true, requireAuth: true,
}, },

View File

@ -7,7 +7,7 @@ export default {
redirect: '/test/unocss', redirect: '/test/unocss',
meta: { meta: {
title: '基础功能测试', title: '基础功能测试',
icon: 'mdi:menu', customIcon: 'logo',
order: 1, order: 1,
}, },
children: [ children: [
@ -17,6 +17,7 @@ export default {
component: () => import('./unocss/index.vue'), component: () => import('./unocss/index.vue'),
meta: { meta: {
title: '测试unocss', title: '测试unocss',
icon: 'material-symbols:auto-awesome-outline-rounded',
}, },
}, },
{ {
@ -25,6 +26,7 @@ export default {
component: () => import('./message/index.vue'), component: () => import('./message/index.vue'),
meta: { meta: {
title: '测试Message', title: '测试Message',
icon: 'material-symbols:auto-awesome-outline-rounded',
}, },
}, },
{ {
@ -33,6 +35,7 @@ export default {
component: () => import('./dialog/index.vue'), component: () => import('./dialog/index.vue'),
meta: { meta: {
title: '测试Dialog', title: '测试Dialog',
icon: 'material-symbols:auto-awesome-outline-rounded',
}, },
}, },
{ {
@ -42,6 +45,7 @@ export default {
meta: { meta: {
title: '测试Keep-Alive', title: '测试Keep-Alive',
keepAlive: true, keepAlive: true,
icon: 'material-symbols:auto-awesome-outline-rounded',
}, },
}, },
], ],