parent
833018a831
commit
c3c975ee11
@ -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': [
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './plugins';
|
||||
export * from './config';
|
||||
export * from './utils';
|
||||
|
@ -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);
|
||||
|
@ -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
20
build/utils/index.ts
Normal 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}`;
|
||||
}
|
@ -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'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
25
package.json
25
package.json
@ -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"
|
||||
}
|
||||
|
921
pnpm-lock.yaml
921
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
1
src/assets/svg/custom-icon.svg
Normal file
1
src/assets/svg/custom-icon.svg
Normal 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 |
24
src/components/custom/SvgIcon.vue
Normal file
24
src/components/custom/SvgIcon.vue
Normal 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>
|
@ -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和字体文件等] */
|
||||
|
2
src/typings/route.d.ts
vendored
2
src/typings/route.d.ts
vendored
@ -94,6 +94,8 @@ declare namespace AuthRoute {
|
||||
keepAlive?: boolean;
|
||||
/** 菜单和面包屑对应的图标 */
|
||||
icon?: string;
|
||||
/** 自定义的菜单和面包屑对应的图标 */
|
||||
customIcon?: string;
|
||||
/** 是否在菜单中隐藏(一些列表、表格的详情页面需要通过参数跳转,所以不能显示在菜单中) */
|
||||
hide?: boolean;
|
||||
/** 外链链接 */
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user