فهرست منبع

feat: 增加了动态配置菜单图标的功能

louhangfei 1 سال پیش
والد
کامیت
9f9780aff7

+ 8 - 5
mock/login/routers.ts

@@ -11,7 +11,7 @@ const list = [
           alwaysShow: false,
           alwaysShow: false,
           frameSrc: '',
           frameSrc: '',
           hidden: false,
           hidden: false,
-          icon: '',
+          icon: 'OverviewIcon',
           isFrame: 0,
           isFrame: 0,
           isRoot: false,
           isRoot: false,
           noCache: false,
           noCache: false,
@@ -31,7 +31,7 @@ const list = [
           alwaysShow: false,
           alwaysShow: false,
           frameSrc: '',
           frameSrc: '',
           hidden: false,
           hidden: false,
-          icon: '',
+          icon: 'RiskPointMonitoringIcon',
           isFrame: 0,
           isFrame: 0,
           isRoot: false,
           isRoot: false,
           noCache: false,
           noCache: false,
@@ -93,7 +93,7 @@ const list = [
           alwaysShow: false,
           alwaysShow: false,
           frameSrc: '',
           frameSrc: '',
           hidden: false,
           hidden: false,
-          icon: '',
+          icon: 'DisasterWarningIcon',
           isFrame: 0,
           isFrame: 0,
           isRoot: false,
           isRoot: false,
           noCache: false,
           noCache: false,
@@ -196,12 +196,15 @@ const list = [
           alwaysShow: false,
           alwaysShow: false,
           frameSrc: '',
           frameSrc: '',
           hidden: false,
           hidden: false,
-          icon: '',
+          icon: 'DisasterPrecaution',
           isFrame: 0,
           isFrame: 0,
           isRoot: false,
           isRoot: false,
           noCache: false,
           noCache: false,
           query: '',
           query: '',
           title: '预防检查',
           title: '预防检查',
+          // OverviewIcon: renderSvg('overview'),
+          // DisasterWarningIcon: renderSvg('disaster-warning'),
+          // DisasterControlIcon: renderSvg('disaster-control'),
         },
         },
         name: '/disaster-prevention/disaster-precaution',
         name: '/disaster-prevention/disaster-precaution',
         parentId: 1022,
         parentId: 1022,
@@ -216,7 +219,7 @@ const list = [
           alwaysShow: false,
           alwaysShow: false,
           frameSrc: '',
           frameSrc: '',
           hidden: false,
           hidden: false,
-          icon: '',
+          icon: 'DisasterControlIcon',
           isFrame: 0,
           isFrame: 0,
           isRoot: false,
           isRoot: false,
           noCache: false,
           noCache: false,

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 14 - 0
src/assets/svg/book.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 17 - 0
src/assets/svg/disaster-control.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 15 - 0
src/assets/svg/disaster-precaution.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 17 - 0
src/assets/svg/disaster-warning.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 14 - 0
src/assets/svg/keys.svg


+ 14 - 0
src/assets/svg/overview.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>lvzhou_gailan</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="切图" transform="translate(-26, -48)">
+            <g id="lvzhou_gailan" transform="translate(26, 48)">
+                <rect id="矩形" fill="currentColor" opacity="0" x="0" y="0" width="20" height="20"></rect>
+                <path d="M5.50082008,2 C3.56646415,2 2,3.56315125 2,5.5 C2,7.43683216 3.56974446,9 5.50410039,9 L9,9 L9,5.50697547 C9,3.57013501 7.435176,2 5.50082008,2 Z" id="路径" fill="currentColor" fill-rule="nonzero"></path>
+                <path d="M18,5.50277967 C18,3.56517699 16.4325218,2 14.5,2 C12.5674782,2 11,3.57629566 11,5.51389834 L11,9 L14.5008607,9 C16.4333825,9 18,7.44038235 18,5.50277967 Z" id="路径" fill="currentColor" fill-rule="nonzero"></path>
+                <path d="M2,14.5 C2,16.4327906 3.67973555,17.9997848 5.75613931,17.9997848 C7.83254306,17.9997848 9.51227862,16.4331713 9.51227862,14.500389 L9.51227862,11 L5.75966051,11 C3.68325675,11 2,12.5672259 2,14.5 L2,14.5 Z M14.2430071,11 L10.4726051,11 L10.4726051,14.500389 C10.4726051,16.4331713 12.1612326,18 14.2376364,18 C16.3140401,18 18,16.4327741 18,14.5 C18,12.5672094 16.3194108,11 14.2430071,11 Z" id="形状" fill="currentColor" fill-rule="nonzero"></path>
+            </g>
+        </g>
+    </g>
+</svg>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 15 - 0
src/assets/svg/risk-point-monitoring.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 14 - 0
src/assets/svg/setting.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 14 - 0
src/assets/svg/user-setting.svg


+ 79 - 0
src/components/SvgIcon/SvgIcon.vue

@@ -0,0 +1,79 @@
+<template>
+  <svg class="svg-icon" aria-hidden="true" :style="{ color: color, width: width, height: height }">
+    <use :xlink:href="svgName" />
+  </svg>
+</template>
+
+<script>
+  import { defineComponent, computed } from 'vue';
+
+  /**
+   * 生效条件:
+   * 1. svg图片要放在src/assets/svg
+   * 2. iconName: svg名称,比如overview.svg,那么iconName就是overview
+   * 3. UI给到的svg一般是自带颜色的。如果想要svg的颜色跟随父组件的color动态改变,
+   * 或者父组件通过css修改svg颜色,必须把svg中的fill颜色值设置为currentColor,也就是fill="currentColor",否则外界无法修改它的颜色
+   * 4. 传入width和heightprops可动态修改svg的大小
+   */
+
+  // const importAll = (requireContext) => requireContext.keys().forEach(requireContext);
+  // try {
+  //     importAll(import.meta.glob('@/assets/icons/*.svg'))
+  // } catch (error) {
+  //     console.log(error)
+  // }
+
+  export default defineComponent({
+    name: 'SvgIcon',
+    props: {
+      iconName: {
+        type: String,
+        required: true,
+      },
+      color: {
+        type: String,
+        required: false,
+      },
+      width: {
+        type: String,
+        required: false,
+        default: '1em',
+      },
+      height: {
+        type: String,
+        required: false,
+        default: '1em',
+      },
+    },
+    setup(props) {
+      const svgName = computed(() => `#icon-${props.iconName}`);
+      return {
+        svgName,
+      };
+    },
+  });
+</script>
+
+<style lang="scss" scoped>
+  .svg-icon {
+    position: relative;
+    width: 1em;
+    height: 1em;
+    overflow: hidden;
+    vertical-align: -0.15em;
+    fill: currentColor;
+    outline-width: 0;
+
+    &::after {
+      position: absolute;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+      z-index: 1;
+      pointer-events: none;
+      cursor: pointer;
+      content: '';
+    }
+  }
+</style>

+ 1 - 0
src/components/SvgIcon/index.ts

@@ -0,0 +1 @@
+export { default as SvgIcon } from './SvgIcon.vue';

+ 31 - 12
src/layout/MenuLayout.vue

@@ -4,19 +4,21 @@
     <aside class="aside">
     <aside class="aside">
       <el-menu :default-active="selectedKeys" :default-openeds="openKeys" router class="el-menu-vertical">
       <el-menu :default-active="selectedKeys" :default-openeds="openKeys" router class="el-menu-vertical">
         <template v-for="item in subMenus" :key="item.path">
         <template v-for="item in subMenus" :key="item.path">
-          <el-sub-menu v-if="item.children && !item.meta?.isHidden" :index="item.path">
-            <template #title>{{ item.meta?.title }}</template>
-            <el-menu-item
-              v-for="child in item.children"
-              :key="child.path"
-              :index="child.path"
-              v-show="!child.meta?.isHidden"
-            >
-              {{ child.meta?.title }}
+          <el-sub-menu v-if="item.children" :index="item.path">
+            <template #title>
+              <component v-if="item.meta.icon" :is="item.meta.icon" />
+              <span class="menu-title">{{ item.meta.title }}</span>
+            </template>
+            <el-menu-item v-for="child in item.children" :key="child.path" :index="child.path">
+              {{ child.meta.title }}
             </el-menu-item>
             </el-menu-item>
           </el-sub-menu>
           </el-sub-menu>
-          <el-menu-item v-else :index="item.path" v-show="!item.meta?.isHidden">
-            {{ item.meta?.title }}
+          <el-menu-item v-else :index="item.path">
+            <!-- <SvgIcon icon-name="overview" /> -->
+            <component v-if="item.meta.icon" :is="item.meta.icon" />
+            <span class="menu-title">
+              {{ item.meta.title }}
+            </span>
           </el-menu-item>
           </el-menu-item>
         </template>
         </template>
       </el-menu>
       </el-menu>
@@ -33,7 +35,6 @@
 
 
   // 当前路由
   // 当前路由
   const currentRoute = useRoute();
   const currentRoute = useRoute();
-  // 获取当前打开的子菜单
   const activeMenu = currentRoute.meta?.activeMenu || null; // activeMenu undefined,null 或 空字符串,统一变为 null。
   const activeMenu = currentRoute.meta?.activeMenu || null; // activeMenu undefined,null 或 空字符串,统一变为 null。
   const selectedKeys = ref<string>((activeMenu ?? currentRoute.name) as string);
   const selectedKeys = ref<string>((activeMenu ?? currentRoute.name) as string);
 
 
@@ -91,4 +92,22 @@
       border-radius: 4cpx 4cpx 0 0;
       border-radius: 4cpx 4cpx 0 0;
     }
     }
   }
   }
+
+  .el-menu-item,
+  .el-sub-menu {
+    svg {
+      color: #1777ff;
+    }
+  }
+  .el-menu-item {
+    &.is-active {
+      svg {
+        color: #fff;
+      }
+    }
+  }
+
+  .menu-title {
+    margin-left: 10px;
+  }
 </style>
 </style>

+ 1 - 1
src/router/full-routes.ts

@@ -43,7 +43,7 @@ export const disasterPreventionRoute = {
         alwaysShow: false,
         alwaysShow: false,
         frameSrc: '',
         frameSrc: '',
         hidden: false,
         hidden: false,
-        icon: '',
+        icon: 'DashboardOutlined',
         isFrame: 0,
         isFrame: 0,
         isRoot: false,
         isRoot: false,
         noCache: false,
         noCache: false,

+ 4 - 4
src/router/generator-routers.ts

@@ -1,11 +1,11 @@
 import { getRouters } from '@/api/system/menu';
 import { getRouters } from '@/api/system/menu';
-// import { constantRouterIcon } from './router-icons';
+import { constantRouterIcon } from './router-icons';
 import { RouteRecordRaw } from 'vue-router';
 import { RouteRecordRaw } from 'vue-router';
 import { Layout, ParentLayout, HomeLayout, MenuLayout } from '@/router/constant';
 import { Layout, ParentLayout, HomeLayout, MenuLayout } from '@/router/constant';
 import type { AppRouteRecordRaw } from '@/router/types';
 import type { AppRouteRecordRaw } from '@/router/types';
 
 
 const Iframe = () => import('@/views/iframe/index.vue');
 const Iframe = () => import('@/views/iframe/index.vue');
-const LayoutMap = new Map<string, () => Promise<any>>();
+const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
 
 
 LayoutMap.set('LAYOUT', Layout);
 LayoutMap.set('LAYOUT', Layout);
 LayoutMap.set('IFRAME', Iframe);
 LayoutMap.set('IFRAME', Iframe);
@@ -18,7 +18,7 @@ LayoutMap.set('MENU_LAYOUT', MenuLayout);
  * @param parent
  * @param parent
  * @returns {*}
  * @returns {*}
  */
  */
-export const routerGenerator = (routerMap: any[], parent?: any): any[] => {
+export const routerGenerator = (routerMap, parent?): any[] => {
   return routerMap.map((item) => {
   return routerMap.map((item) => {
     const currentRouter: any = {
     const currentRouter: any = {
       // 路由地址 动态拼接生成如 /dashboard/workplace
       // 路由地址 动态拼接生成如 /dashboard/workplace
@@ -31,7 +31,7 @@ export const routerGenerator = (routerMap: any[], parent?: any): any[] => {
       meta: {
       meta: {
         ...item.meta,
         ...item.meta,
         label: item.meta.title,
         label: item.meta.title,
-        // icon: constantRouterIcon[item.meta.icon] || null,
+        icon: constantRouterIcon[item.meta.icon] || item.meta.icon || null,
         permissions: item.meta.permissions || null,
         permissions: item.meta.permissions || null,
       },
       },
     };
     };

+ 64 - 62
src/router/router-icons.ts

@@ -1,63 +1,65 @@
-// import { renderIcon, renderImg } from '@/utils/index';
-// import HandleFeedback from '@/assets/icons/handleFeedback.png';
-// import Handle from '@/assets/icons/handle.png';
-// import {
-//   DashboardOutlined,
-//   ExclamationCircleOutlined,
-//   ProfileOutlined,
-//   CheckCircleOutlined,
-//   SettingOutlined,
-//   ControlOutlined,
-//   ProjectOutlined,
-//   WalletOutlined,
-//   TableOutlined,
-//   SafetyOutlined,
-//   BellOutlined,
-//   BookOutlined,
-//   ToolOutlined,
-//   PictureOutlined,
-//   CameraOutlined,
-//   ApartmentOutlined,
-//   UserOutlined,
-//   FunctionOutlined,
-//   SendOutlined,
-//   LineChartOutlined,
-// } from '@vicons/antd';
-// import {
-//   OptionsSharp,
-//   DesktopOutline,
-//   DocumentTextOutline,
-//   DiamondOutline,
-//   FileTrayFullOutline,
-// } from '@vicons/ionicons5';
+import { renderIcon, renderImg, renderSvg } from '@/utils/index';
 
 
-// //前端路由图标映射表
-// export const constantRouterIcon = {
-//   DashboardOutlined: renderIcon(DashboardOutlined),
-//   OptionsSharp: renderIcon(OptionsSharp),
-//   ExclamationCircleOutlined: renderIcon(ExclamationCircleOutlined),
-//   ProfileOutlined: renderIcon(ProfileOutlined),
-//   CheckCircleOutlined: renderIcon(CheckCircleOutlined),
-//   SettingOutlined: renderIcon(SettingOutlined),
-//   ControlOutlined: renderIcon(ControlOutlined),
-//   WalletOutlined: renderIcon(WalletOutlined),
-//   DesktopOutline: renderIcon(DesktopOutline),
-//   ProjectOutlined: renderIcon(ProjectOutlined),
-//   DocumentTextOutline: renderIcon(DocumentTextOutline),
-//   DiamondOutline: renderIcon(DiamondOutline),
-//   TableOutlined: renderIcon(TableOutlined),
-//   SafetyOutlined: renderIcon(SafetyOutlined),
-//   BellOutlined: renderIcon(BellOutlined),
-//   BookOutlined: renderIcon(BookOutlined),
-//   ToolOutlined: renderIcon(ToolOutlined),
-//   PictureOutlined: renderIcon(PictureOutlined),
-//   CameraOutlined: renderIcon(CameraOutlined),
-//   ApartmentOutlined: renderIcon(ApartmentOutlined),
-//   UserOutlined: renderIcon(UserOutlined),
-//   FunctionOutlined: renderIcon(FunctionOutlined),
-//   SendOutlined: renderIcon(SendOutlined),
-//   LineChartOutlined: renderIcon(LineChartOutlined),
-//   FileTrayFullOutline: renderIcon(FileTrayFullOutline),
-//   HandleFeedback: renderImg(HandleFeedback),
-//   Handle: renderImg(Handle),
-// };
+import {
+  DashboardOutlined,
+  ExclamationCircleOutlined,
+  ProfileOutlined,
+  CheckCircleOutlined,
+  SettingOutlined,
+  ControlOutlined,
+  ProjectOutlined,
+  WalletOutlined,
+  TableOutlined,
+  SafetyOutlined,
+  BellOutlined,
+  BookOutlined,
+  ToolOutlined,
+  PictureOutlined,
+  CameraOutlined,
+  ApartmentOutlined,
+  UserOutlined,
+  FunctionOutlined,
+  SendOutlined,
+  LineChartOutlined,
+} from '@vicons/antd';
+import {
+  OptionsSharp,
+  DesktopOutline,
+  DocumentTextOutline,
+  DiamondOutline,
+  FileTrayFullOutline,
+} from '@vicons/ionicons5';
+
+//前端路由图标映射表
+export const constantRouterIcon = {
+  DashboardOutlined: renderIcon(DashboardOutlined),
+  OptionsSharp: renderIcon(OptionsSharp),
+  ExclamationCircleOutlined: renderIcon(ExclamationCircleOutlined),
+  ProfileOutlined: renderIcon(ProfileOutlined),
+  CheckCircleOutlined: renderIcon(CheckCircleOutlined),
+  SettingOutlined: renderIcon(SettingOutlined),
+  ControlOutlined: renderIcon(ControlOutlined),
+  WalletOutlined: renderIcon(WalletOutlined),
+  DesktopOutline: renderIcon(DesktopOutline),
+  ProjectOutlined: renderIcon(ProjectOutlined),
+  DocumentTextOutline: renderIcon(DocumentTextOutline),
+  DiamondOutline: renderIcon(DiamondOutline),
+  TableOutlined: renderIcon(TableOutlined),
+  SafetyOutlined: renderIcon(SafetyOutlined),
+  BellOutlined: renderIcon(BellOutlined),
+  BookOutlined: renderIcon(BookOutlined),
+  ToolOutlined: renderIcon(ToolOutlined),
+  PictureOutlined: renderIcon(PictureOutlined),
+  CameraOutlined: renderIcon(CameraOutlined),
+  ApartmentOutlined: renderIcon(ApartmentOutlined),
+  UserOutlined: renderIcon(UserOutlined),
+  FunctionOutlined: renderIcon(FunctionOutlined),
+  SendOutlined: renderIcon(SendOutlined),
+  LineChartOutlined: renderIcon(LineChartOutlined),
+  FileTrayFullOutline: renderIcon(FileTrayFullOutline),
+  OverviewIcon: renderSvg('overview'),
+  RiskPointMonitoringIcon: renderSvg('risk-point-monitoring'),
+  DisasterWarningIcon: renderSvg('disaster-warning'),
+  DisasterControlIcon: renderSvg('disaster-control'),
+  DisasterPrecaution: renderSvg('disaster-precaution'),
+};

+ 10 - 6
src/utils/index.ts

@@ -5,6 +5,7 @@ import { PageEnum } from '@/enums/pageEnum';
 import { isObject, isString, isNumber } from './is/index';
 import { isObject, isString, isNumber } from './is/index';
 import { cloneDeep } from 'lodash-es';
 import { cloneDeep } from 'lodash-es';
 import { TargetContext } from '/#/index';
 import { TargetContext } from '/#/index';
+import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
 
 
 /**
 /**
  * render 图标
  * render 图标
@@ -38,6 +39,11 @@ export function renderNew(type = 'warning', text = 'New', color: object = newTag
     );
     );
 }
 }
 
 
+export function renderSvg(svgName: string) {
+  // <SvgIcon class="filter-btn" icon-name="filter" :color="algoOpened || algoClosed ? '#409EFF' : ''" />
+  return () => h(SvgIcon, { iconName: svgName, width: '20px', height: '20px' });
+}
+
 /**
 /**
  * 递归组装菜单格式
  * 递归组装菜单格式
  */
  */
@@ -214,10 +220,10 @@ function addLight(color: string, amount: number) {
 export function lighten(color: string, amount: number) {
 export function lighten(color: string, amount: number) {
   color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
   color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
   amount = Math.trunc((255 * amount) / 100);
   amount = Math.trunc((255 * amount) / 100);
-  return `#${addLight(color.substring(0, 2), amount)}${addLight(
-    color.substring(2, 4),
+  return `#${addLight(color.substring(0, 2), amount)}${addLight(color.substring(2, 4), amount)}${addLight(
+    color.substring(4, 6),
     amount,
     amount,
-  )}${addLight(color.substring(4, 6), amount)}`;
+  )}`;
 }
 }
 
 
 export function openWindow(
 export function openWindow(
@@ -236,9 +242,7 @@ export function openWindow(
  * 处理css单位
  * 处理css单位
  * */
  * */
 export function cssUnit(value: string | number, unit = 'px') {
 export function cssUnit(value: string | number, unit = 'px') {
-  return isNumber(value) || (isString(value) && value.indexOf(unit as string) === -1)
-    ? `${value}${unit}`
-    : value;
+  return isNumber(value) || (isString(value) && value.indexOf(unit as string) === -1) ? `${value}${unit}` : value;
 }
 }
 
 
 /**
 /**

+ 1 - 1
vite.config.ts

@@ -13,7 +13,7 @@ import postcssPxToViewport from 'postcss-px-to-viewport';
 
 
 const svg = createSvgIconsPlugin({
 const svg = createSvgIconsPlugin({
   // 要缓存的图标文件夹
   // 要缓存的图标文件夹
-  iconDirs: [path.resolve(__dirname, 'src/assets/icons')],
+  iconDirs: [path.resolve(__dirname, 'src/assets/svg')],
   // 执行 icon name 的格式
   // 执行 icon name 的格式
   symbolId: 'icon-[name]',
   symbolId: 'icon-[name]',
 });
 });