feat(projects): 本地svg动态渲染图标

ISSUES CLOSED: #61
This commit is contained in:
Soybean 2022-06-16 01:17:31 +08:00
parent 833018a831
commit c3c975ee11
16 changed files with 1067 additions and 63 deletions

View File

@ -182,7 +182,7 @@ module.exports = {
pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'vuex', 'pinia', 'naive-ui']
}
],
'import/no-unresolved': ['error', { ignore: ['uno.css', '~icons/*'] }],
'import/no-unresolved': ['error', { ignore: ['uno.css', '~icons/*', 'virtual:svg-icons-register'] }],
'import/prefer-default-export': 'off',
'max-classes-per-file': 'off',
'no-param-reassign': [

View File

@ -1,2 +1,3 @@
export * from './plugins';
export * from './config';
export * from './utils';

View File

@ -10,10 +10,9 @@ import compress from './compress';
/**
* vite插件
* @param viteEnv -
* @param srcPath - src路径
*/
export function setupVitePlugins(viteEnv: ImportMetaEnv, srcPath: string): (PluginOption | PluginOption[])[] {
const plugins = [...vue, html(viteEnv), ...unplugin(srcPath), unocss, mock];
export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | PluginOption[])[] {
const plugins = [...vue, html(viteEnv), ...unplugin, unocss, mock];
if (viteEnv.VITE_VISUALIZER === 'true') {
plugins.push(visualizer);

View File

@ -4,22 +4,32 @@ import IconsResolver from 'unplugin-icons/resolver';
import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import { getSrcPath } from '../utils';
export default (srcPath: string) => {
return [
DefineOptions(),
Icons({
compiler: 'vue3',
customCollections: {
custom: FileSystemIconLoader(`${srcPath}/assets/svg`)
},
scale: 1,
defaultClass: 'inline-block'
}),
Components({
dts: 'src/typings/components.d.ts',
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
})
];
};
const srcPath = getSrcPath();
const customIconPath = `${srcPath}/assets/svg`;
export default [
DefineOptions(),
Icons({
compiler: 'vue3',
customCollections: {
custom: FileSystemIconLoader(customIconPath)
},
scale: 1,
defaultClass: 'inline-block'
}),
Components({
dts: 'src/typings/components.d.ts',
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
}),
createSvgIconsPlugin({
iconDirs: [customIconPath],
symbolId: 'icon-custom-[dir]-[name]',
inject: 'body-last',
customDomId: '__CUSTOM_SVG_ICON__'
})
];

20
build/utils/index.ts Normal file
View File

@ -0,0 +1,20 @@
import path from 'path';
/**
*
* @descrition
*/
export function getRootPath() {
return path.resolve(process.cwd());
}
/**
* src路径
* @param srcName - src目录名称(: "src")
* @descrition
*/
export function getSrcPath(srcName = 'src') {
const rootPath = getRootPath();
return `${rootPath}/${srcName}`;
}

View File

@ -250,7 +250,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
meta: {
title: '图标',
requiresAuth: true,
icon: 'ic:baseline-insert-emoticon'
customIcon: 'custom-icon'
}
},
{
@ -709,7 +709,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
meta: {
title: '图标',
requiresAuth: true,
icon: 'ic:baseline-insert-emoticon'
customIcon: 'custom-icon'
}
},
{

View File

@ -1,11 +1,11 @@
{
"name": "soybean-admin",
"version": "0.9.5",
"author": {
"name": "Soybean",
"email": "honghuangdc@gmail.com",
"url": "https://github.com/honghuangdc"
},
"author": {
"name": "Soybean",
"email": "honghuangdc@gmail.com",
"url": "https://github.com/honghuangdc"
},
"scripts": {
"dev": "cross-env VITE_ENV_TYPE=dev vite",
"dev:test": "cross-env VITE_ENV_TYPE=test vite",
@ -103,15 +103,16 @@
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-icons": "^2.0.1",
"vue-eslint-parser": "^9.0.2",
"vue-tsc": "^0.37.8"
},
"homepage": "https://github.com/honghuangdc/soybean-admin",
"repository": {
"url": "https://github.com/honghuangdc/soybean-admin.git"
},
"bugs": {
"url": "https://github.com/honghuangdc/soybean-admin/issues"
},
"homepage": "https://github.com/honghuangdc/soybean-admin",
"repository": {
"url": "https://github.com/honghuangdc/soybean-admin.git"
},
"bugs": {
"url": "https://github.com/honghuangdc/soybean-admin/issues"
},
"license": "MIT"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="M19 10c0 1.38-2.12 2.5-3.5 2.5s-2.75-1.12-2.75-2.5h-1.5c0 1.38-1.37 2.5-2.75 2.5S5 11.38 5 10h-.75c-.16.64-.25 1.31-.25 2a8 8 0 0 0 8 8a8 8 0 0 0 8-8c0-.69-.09-1.36-.25-2H19m-7-6C9.04 4 6.45 5.61 5.07 8h13.86C17.55 5.61 14.96 4 12 4m10 8a10 10 0 0 1-10 10A10 10 0 0 1 2 12A10 10 0 0 1 12 2a10 10 0 0 1 10 10m-10 5.23c-1.75 0-3.29-.73-4.19-1.81L9.23 14c.45.72 1.52 1.23 2.77 1.23s2.32-.51 2.77-1.23l1.42 1.42c-.9 1.08-2.44 1.81-4.19 1.81Z"></path></svg>

After

Width:  |  Height:  |  Size: 702 B

View File

@ -0,0 +1,24 @@
<template>
<svg aria-hidden="true" width="1em" height="1em" class="inline-block">
<use :xlink:href="symbolId" fill="currentColor" />
</svg>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
/** 前缀 */
prefix?: string;
/** 图标名称(图片的文件名) */
icon: string;
}
const props = withDefaults(defineProps<Props>(), {
prefix: 'icon-custom'
});
const symbolId = computed(() => `#${props.prefix}-${props.icon}`);
</script>
<style scoped></style>

View File

@ -2,6 +2,7 @@ import 'uno.css';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import 'virtual:svg-icons-register';
import '../styles/css/global.css';
/** import static assets: css, js , font and so on. - [引入静态资源css、js和字体文件等] */

View File

@ -94,6 +94,8 @@ declare namespace AuthRoute {
keepAlive?: boolean;
/** 菜单和面包屑对应的图标 */
icon?: string;
/** 自定义的菜单和面包屑对应的图标 */
customIcon?: string;
/** 是否在菜单中隐藏(一些列表、表格的详情页面需要通过参数跳转,所以不能显示在菜单中) */
hide?: boolean;
/** 外链链接 */

View File

@ -1,5 +1,6 @@
import { h } from 'vue';
import { Icon } from '@iconify/vue';
import SvgIcon from '@/components/custom/SvgIcon.vue';
/**
* iconify
@ -17,3 +18,21 @@ export function iconifyRender(icon: string, color?: string, size?: number) {
}
return () => h(Icon, { icon, style });
}
/**
*
* @param icon -
* @param color -
* @param size -
*/
export function customIconRender(icon: string, color?: string, size?: number) {
const style: { color?: string; size?: string } = {};
if (color) {
style.color = color;
}
if (size) {
style.size = `${size}px`;
}
return () => h(SvgIcon, { icon, style });
}

View File

@ -1,4 +1,4 @@
import { iconifyRender } from '../common';
import { iconifyRender, customIconRender } from '../common';
/** 路由不转换菜单 */
function hideInMenu(route: AuthRoute.Route) {
@ -6,13 +6,21 @@ function hideInMenu(route: AuthRoute.Route) {
}
/** 给菜单添加可选属性 */
function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: GlobalMenuOption[]) {
const item = { ...menuItem };
if (icon) {
Object.assign(item, { icon: iconifyRender(icon) });
function addPartialProps(config: {
menu: GlobalMenuOption;
icon?: string;
customIcon?: string;
children?: GlobalMenuOption[];
}) {
const item = { ...config.menu };
if (config.icon) {
Object.assign(item, { icon: iconifyRender(config.icon) });
}
if (children) {
Object.assign(item, { children });
if (config.customIcon) {
Object.assign(item, { icon: customIconRender(config.customIcon) });
}
if (config.children) {
Object.assign(item, { children: config.children });
}
return item;
}
@ -30,16 +38,17 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuO
if (route.children) {
menuChildren = transformAuthRouteToMenu(route.children);
}
const menuItem: GlobalMenuOption = addPartialProps(
{
const menuItem: GlobalMenuOption = addPartialProps({
menu: {
key: routeName,
label: meta.title,
routeName,
routePath: path
},
meta?.icon,
menuChildren
);
icon: meta.icon,
customIcon: meta.customIcon,
children: menuChildren
});
if (!hideInMenu(route)) {
globalMenu.push(menuItem);

View File

@ -16,9 +16,9 @@
<web-site-link label="iconify地址" link="https://icones.js.org/" class="mt-10px" />
</template>
</n-card>
<n-card title="SvgIcon 示例" class="mt-10px shadow-sm rounded-16px">
<n-card title="自定义图标示例" class="mt-10px shadow-sm rounded-16px">
<div class="pb-12px text-16px">
在src/assets/svg文件夹下的svg文件通过在template里面以 icon-custom-{文件名} 直接渲染动态渲染需要import组件
在src/assets/svg文件夹下的svg文件通过在template里面以 icon-custom-{文件名} 直接渲染
</div>
<div class="grid grid-cols-10">
<div class="mt-5px flex-x-center">
@ -27,8 +27,11 @@
<div class="mt-5px flex-x-center">
<icon-custom-cast class="text-20px text-error" />
</div>
</div>
<div class="py-12px text-16px">通过SvgIcon组件动态渲染, 菜单通过meta的customIcon属性渲染自定义图标</div>
<div class="grid grid-cols-10">
<div v-for="(item, index) in customIcons" :key="index" class="mt-5px flex-x-center">
<component :is="item" class="text-30px text-primary" />
<svg-icon :icon="item" class="text-30px text-primary" />
</div>
</div>
</n-card>
@ -39,16 +42,10 @@
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
import { icons } from './icons';
import CustomActivity from '~icons/custom/activity.svg';
import CustomAtSign from '~icons/custom/at-sign.svg';
import CustomCast from '~icons/custom/cast.svg';
import CustomChrome from '~icons/custom/chrome.svg';
import CustomCopy from '~icons/custom/copy.svg';
import CustomWind from '~icons/custom/wind.svg';
const selectValue = ref('');
const customIcons = [CustomActivity, CustomAtSign, CustomCast, CustomChrome, CustomCopy, CustomWind];
const customIcons = ['custom-icon', 'activity', 'at-sign', 'cast', 'chrome', 'copy', 'wind'];
</script>
<style scoped></style>

View File

@ -1,13 +1,12 @@
import { fileURLToPath } from 'url';
import { defineConfig, loadEnv } from 'vite';
import { viteDefine, setupVitePlugins, createViteProxy } from './build';
import { getRootPath, getSrcPath, viteDefine, setupVitePlugins, createViteProxy } from './build';
import { getEnvConfig } from './.env-config';
export default defineConfig(configEnv => {
const viteEnv = loadEnv(configEnv.mode, process.cwd()) as ImportMetaEnv;
const rootPath = fileURLToPath(new URL('./', import.meta.url));
const srcPath = `${rootPath}src`;
const rootPath = getRootPath();
const srcPath = getSrcPath();
const isOpenProxy = viteEnv.VITE_HTTP_PROXY === 'true';
const envConfig = getEnvConfig(viteEnv);
@ -21,7 +20,7 @@ export default defineConfig(configEnv => {
}
},
define: viteDefine,
plugins: setupVitePlugins(viteEnv, srcPath),
plugins: setupVitePlugins(viteEnv),
css: {
preprocessorOptions: {
scss: {