mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 07:43:42 +08:00 
			
		
		
		
	@@ -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
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										921
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												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: {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user