ソースを参照

新增图标资源和菜单项图标显示优化

- 新增Flag图标和Bootstrap图标资源
- 优化垂直菜单中的图标显示,支持bi前缀图标
- 调整图标样式,提高对齐和间距
WanGxC 8 ヶ月 前
コミット
d2847d4cf2

+ 211 - 176
src/components/iconSelector/index.vue

@@ -1,55 +1,67 @@
 <template>
-	<div class="icon-selector w100 h100">
-		<el-input
-			v-model="state.fontIconSearch"
-			:placeholder="state.fontIconPlaceholder"
-			:clearable="clearable"
-			:disabled="disabled"
-			:size="size"
-			ref="inputWidthRef"
-			@clear="onClearFontIcon"
-			@focus="onIconFocus"
-			@blur="onIconBlur"
-		>
-			<template #prepend>
-				<SvgIcon
-					:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
-					class="font14"
-					v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
-				/>
-				<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
-			</template>
-		</el-input>
-		<el-popover
-			placement="bottom"
-			:width="state.fontIconWidth"
-			transition="el-zoom-in-top"
-			popper-class="icon-selector-popper"
-			trigger="click"
-			:virtual-ref="inputWidthRef"
-			virtual-triggering
-		>
-			<template #default>
-				<div class="icon-selector-warp">
-					<div class="icon-selector-warp-title">{{ title }}</div>
-					<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
-						<el-tab-pane lazy label="ali" name="ali">
-							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
-						</el-tab-pane>
-						<el-tab-pane lazy label="ele" name="ele">
-							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
-						</el-tab-pane>
-						<el-tab-pane lazy label="awe" name="awe">
-							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
-						</el-tab-pane>
-					</el-tabs>
-				</div>
-			</template>
-		</el-popover>
-	</div>
+  <div class="icon-selector w100 h100">
+    <el-input
+        ref="inputWidthRef"
+        v-model="state.fontIconSearch"
+        :clearable="clearable"
+        :disabled="disabled"
+        :placeholder="state.fontIconPlaceholder"
+        :size="size"
+        @blur="onIconBlur"
+        @clear="onClearFontIcon"
+        @click="clickInit"
+        @focus="onIconFocus"
+    >
+      <template #prepend>
+        <SvgIcon
+            v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
+            :name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
+            class="font14"
+        />
+        <i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
+      </template>
+    </el-input>
+    <el-popover
+        :virtual-ref="inputWidthRef"
+        :width="state.fontIconWidth"
+        placement="bottom"
+        popper-class="icon-selector-popper"
+        transition="el-zoom-in-top"
+        trigger="click"
+        virtual-triggering
+    >
+      <template #default>
+        <div class="icon-selector-warp">
+          <!--<div class="icon-selector-warp-title mb-2">{{ title }}</div>-->
+          <el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
+            <el-tab-pane label="ali" name="ali">
+              <IconList :empty="emptyDescription" :list="fontIconSheetsFilterList" :prefix="state.fontIconPrefix"
+                        @get-icon="onColClick"/>
+            </el-tab-pane>
+            <el-tab-pane label="ele" name="ele">
+              <IconList :empty="emptyDescription" :list="fontIconSheetsFilterList" :prefix="state.fontIconPrefix"
+                        @get-icon="onColClick"/>
+            </el-tab-pane>
+            <el-tab-pane label="awe" name="awe">
+              <IconList :empty="emptyDescription" :list="fontIconSheetsFilterList" :prefix="state.fontIconPrefix"
+                        @get-icon="onColClick"/>
+            </el-tab-pane>
+            <el-tab-pane label="flag" name="flag">
+              <IconList :empty="emptyDescription" :list="fontIconSheetsFilterList" :prefix="state.fontIconPrefix"
+                        @get-icon="onColClick"/>
+            </el-tab-pane>
+            <el-tab-pane label="bootstrap" name="bootstrap">
+              <IconList :empty="emptyDescription" :list="fontIconSheetsFilterList" :prefix="state.fontIconPrefix"
+                        @get-icon="onColClick"/>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </template>
+    </el-popover>
+  </div>
 </template>
 
-<script setup lang="ts" name="iconSelector">
+<script lang="ts" name="iconSelector" setup>
 import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
 import type { TabsPaneContext } from 'element-plus';
 import initIconfont from '/@/utils/getStyleSheets';
@@ -57,49 +69,49 @@ import '/@/theme/iconSelector.scss';
 
 // 定义父组件传过来的值
 const props = defineProps({
-	// 输入框前置内容
-	prepend: {
-		type: String,
-		default: () => 'ele-Pointer',
-	},
-	// 输入框占位文本
-	placeholder: {
-		type: String,
-		default: () => '请输入内容搜索图标或者选择图标',
-	},
-	// 输入框占位文本
-	size: {
-		type: String,
-		default: () => 'default',
-	},
-	// 弹窗标题
-	title: {
-		type: String,
-		default: () => '请选择图标',
-	},
-	// 禁用
-	disabled: {
-		type: Boolean,
-		default: () => false,
-	},
-	// 是否可清空
-	clearable: {
-		type: Boolean,
-		default: () => true,
-	},
-	// 自定义空状态描述文字
-	emptyDescription: {
-		type: String,
-		default: () => '无相关图标',
-	},
-	// 双向绑定值,默认为 modelValue,
-	// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
-	// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
-	modelValue: String,
+  // 输入框前置内容
+  prepend: {
+    type: String,
+    default: () => 'ele-Pointer'
+  },
+  // 输入框占位文本
+  placeholder: {
+    type: String,
+    default: () => '请输入内容搜索图标或者选择图标'
+  },
+  // 输入框占位文本
+  size: {
+    type: String,
+    default: () => 'default'
+  },
+  // 弹窗标题
+  title: {
+    type: String,
+    default: () => '请选择图标'
+  },
+  // 禁用
+  disabled: {
+    type: Boolean,
+    default: () => false
+  },
+  // 是否可清空
+  clearable: {
+    type: Boolean,
+    default: () => true
+  },
+  // 自定义空状态描述文字
+  emptyDescription: {
+    type: String,
+    default: () => '无相关图标'
+  },
+  // 双向绑定值,默认为 modelValue,
+  // 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
+  // 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
+  modelValue: String
 });
 
 // 定义子组件向父组件传值/事件
-const emit = defineEmits(['update:modelValue', 'get', 'clear']);
+const emit = defineEmits([ 'update:modelValue', 'get', 'clear' ]);
 
 // 引入组件
 const IconList = defineAsyncComponent(() => import('/@/components/iconSelector/list.vue'));
@@ -107,135 +119,158 @@ const IconList = defineAsyncComponent(() => import('/@/components/iconSelector/l
 // 定义变量内容
 const inputWidthRef = ref();
 const state = reactive({
-	fontIconPrefix: '',
-	fontIconWidth: 0,
-	fontIconSearch: '',
-	fontIconPlaceholder: '',
-	fontIconTabActive: 'ali',
-	fontIconList: {
-		ali: [],
-		ele: [],
-		awe: [],
-	},
+  fontIconPrefix: '',
+  fontIconWidth: 0,
+  fontIconSearch: '',
+  fontIconPlaceholder: '',
+  fontIconTabActive: 'ali',
+  fontIconList: {
+    ali: [],
+    ele: [],
+    awe: [],
+    flag: [],
+    bootstrap: []
+  },
 });
 
 // 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
 const onIconFocus = () => {
-	if (!props.modelValue) return false;
-	state.fontIconSearch = '';
-	state.fontIconPlaceholder = props.modelValue;
+  if (!props.modelValue) return false;
+  state.fontIconSearch = '';
+  state.fontIconPlaceholder = props.modelValue;
 };
 // 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
 const onIconBlur = () => {
-	const list = fontIconTabNameList();
-	setTimeout(() => {
-		const icon = list.filter((icon: string) => icon === state.fontIconSearch);
-		if (icon.length <= 0) state.fontIconSearch = '';
-	}, 300);
+  const list = fontIconTabNameList();
+  setTimeout(() => {
+    const icon = list.filter((icon: string) => icon === state.fontIconSearch);
+    if (icon.length <= 0) state.fontIconSearch = '';
+  }, 300);
 };
 // 图标搜索及图标数据显示
 const fontIconSheetsFilterList = computed(() => {
-	const list = fontIconTabNameList();
-	if (!state.fontIconSearch) return list;
-	let search = state.fontIconSearch.trim().toLowerCase();
-	return list.filter((item: string) => {
-		if (item.toLowerCase().indexOf(search) !== -1) return item;
-	});
+  const list = fontIconTabNameList();
+  if (!state.fontIconSearch) return list;
+  let search = state.fontIconSearch.trim().toLowerCase();
+  return list.filter((item: string) => {
+    if (item.toLowerCase().indexOf(search) !== -1) return item;
+  });
 });
 // 根据 tab name 类型设置图标
 const fontIconTabNameList = () => {
-	let iconList: any = [];
-	if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
-	else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
-	else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
-	return iconList;
+  let iconList: any = [];
+  if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
+  else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
+  else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
+  else if (state.fontIconTabActive === 'flag') iconList = state.fontIconList.flag;
+  else if (state.fontIconTabActive === 'bootstrap') iconList = state.fontIconList.bootstrap;
+  return iconList;
 };
 // 处理 icon 双向绑定数值回显
 const initModeValueEcho = () => {
-	if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
-	(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
-	(<string | undefined>state.fontIconPrefix) = props.modelValue;
+  if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
+  (<string | undefined>state.fontIconPlaceholder) = props.modelValue;
+  (<string | undefined>state.fontIconPrefix) = props.modelValue;
 };
 // 处理 icon 类型,用于回显时,tab 高亮与初始化数据
 const initFontIconName = () => {
-	let name = 'ali';
-	if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
-	else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
-	else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
-	// 初始化 tab 高亮回显
-	state.fontIconTabActive = name;
-	return name;
+  let name = 'ali';
+  if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
+  else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
+  else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
+  else if (props.modelValue!.indexOf('fi') > -1) name = 'flag';
+  else if (props.modelValue!.indexOf('bi') > -1) name = 'bootstrap';
+  // 初始化 tab 高亮回显
+  state.fontIconTabActive = name;
+  return name;
 };
 // 初始化数据
 const initFontIconData = async (name: string) => {
-	if (name === 'ali') {
-		// 阿里字体图标使用 `iconfont xxx`
-		if (state.fontIconList.ali.length > 0) return;
-		await initIconfont.ali().then((res: any) => {
-			state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
-		});
-	} else if (name === 'ele') {
-		// element plus 图标
-		if (state.fontIconList.ele.length > 0) return;
-		await initIconfont.ele().then((res: any) => {
-			state.fontIconList.ele = res;
-		});
-	} else if (name === 'awe') {
-		// fontawesome字体图标使用 `fa xxx`
-		if (state.fontIconList.awe.length > 0) return;
-		await initIconfont.awe().then((res: any) => {
-			state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
-		});
-	}
-	// 初始化 input 的 placeholder
-	// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
-	state.fontIconPlaceholder = props.placeholder;
-	// 初始化双向绑定回显
-	initModeValueEcho();
+  if (name === 'ali') {
+    // 阿里字体图标使用 `iconfont xxx`
+    if (state.fontIconList.ali.length > 0) return;
+    await initIconfont.ali().then((res: any) => {
+      state.fontIconList.ali = res.map((i: string) => `iconfont ${ i }`);
+    });
+  } else if (name === 'ele') {
+    // element plus 图标
+    if (state.fontIconList.ele.length > 0) return;
+    await initIconfont.ele().then((res: any) => {
+      state.fontIconList.ele = res;
+    });
+  } else if (name === 'awe') {
+    // fontawesome字体图标使用 `fa xxx`
+    if (state.fontIconList.awe.length > 0) return;
+    await initIconfont.awe().then((res: any) => {
+      state.fontIconList.awe = res.map((i: string) => `fa ${ i }`);
+    });
+  } else if (name === 'flag') {
+    // flag字体图标使用 `fi fi-xx`
+    if (state.fontIconList.flag.length > 0) return;
+    await initIconfont.flag().then((res: any) => {
+      state.fontIconList.flag = res.map((i: string) => `fi ${ i }`);
+    });
+  } else if (name === 'bootstrap') {
+    // flag字体图标使用 `bi`
+    if (state.fontIconList.bootstrap.length > 0) return;
+    await initIconfont.bootstrap().then((res: any) => {
+      state.fontIconList.bootstrap = res.map((i: string) => `bi ${ i }`);
+    });
+  }
+  // 初始化 input 的 placeholder
+  // 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
+  state.fontIconPlaceholder = props.placeholder;
+  // 初始化双向绑定回显
+  initModeValueEcho();
 };
 // 图标点击切换
 const onIconClick = (pane: TabsPaneContext) => {
-	initFontIconData(pane.paneName as string);
-	inputWidthRef.value.focus();
+  initFontIconData(pane.paneName as string);
+  inputWidthRef.value.focus();
 };
 // 获取当前点击的 icon 图标
 const onColClick = (v: string) => {
-	state.fontIconPlaceholder = v;
-	state.fontIconPrefix = v;
-	emit('get', state.fontIconPrefix);
-	emit('update:modelValue', state.fontIconPrefix);
-	inputWidthRef.value.focus();
+  state.fontIconPlaceholder = v;
+  state.fontIconPrefix = v;
+  emit('get', state.fontIconPrefix);
+  emit('update:modelValue', state.fontIconPrefix);
+  inputWidthRef.value.focus();
 };
 // 清空当前点击的 icon 图标
 const onClearFontIcon = () => {
-	state.fontIconPrefix = '';
-	emit('clear', state.fontIconPrefix);
-	emit('update:modelValue', state.fontIconPrefix);
+  state.fontIconPrefix = '';
+  emit('clear', state.fontIconPrefix);
+  emit('update:modelValue', state.fontIconPrefix);
 };
 // 获取 input 的宽度
 const getInputWidth = () => {
-	nextTick(() => {
-		state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
-	});
+  nextTick(() => {
+    state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
+  });
 };
 // 监听页面宽度改变
 const initResize = () => {
-	window.addEventListener('resize', () => {
-		getInputWidth();
-	});
+  window.addEventListener('resize', () => {
+    getInputWidth();
+  });
 };
+
+function clickInit() {
+  initFontIconData(initFontIconName());
+}
+
 // 页面加载时
 onMounted(() => {
-	initFontIconData(initFontIconName());
-	initResize();
-	getInputWidth();
+  initResize();
+  getInputWidth();
+  // initFontIconData(initFontIconName());
 });
 // 监听双向绑定 modelValue 的变化
 watch(
-	() => props.modelValue,
-	() => {
-		initModeValueEcho();
-		initFontIconName();
-	}
+    () => props.modelValue,
+    () => {
+      initModeValueEcho();
+      initFontIconName();
+    }
 );
 </script>

+ 23 - 12
src/layout/navMenu/vertical.vue

@@ -19,18 +19,26 @@
       </el-sub-menu>
       
       <template v-else>
-        <div class="rounded-md">
-          <div class="menu-hover w-full">
-            <el-menu-item :key="val.path" :index="val.path">
-              <SvgIcon :name="val.meta.icon"/>
-              <template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)" #title>
-                <span>{{ $t(val.meta.title) }}</span>
-              </template>
-              <template v-else #title>
-                <a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a>
-              </template>
-            </el-menu-item>
-          </div>
+        <div class="menu-hover rounded-md">
+          <el-menu-item :key="val.path" :index="val.path" class="">
+            <SvgIcon
+                :name="val.meta.icon"
+                :style="[
+                  val.meta.icon.startsWith('bi') ? {
+                    'vertical-align': 'middle',
+                    'margin-right': '5px',
+                    'width': '24px',
+                    'text-align': 'center',
+                  } : {}
+                ]"
+            />
+            <template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)" #title>
+              <span>{{ $t(val.meta.title) }}</span>
+            </template>
+            <template v-else #title>
+              <a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a>
+            </template>
+          </el-menu-item>
         </div>
       </template>
     </template>
@@ -44,6 +52,8 @@ import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import other from '/@/utils/other';
 
+
+
 // 引入组件
 const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
 
@@ -70,6 +80,7 @@ const state = reactive({
 const menuLists = computed(() => {
   return <RouteItems>props.menuList;
 });
+console.log('menuLists=> ', menuLists);
 // 获取布局配置信息
 const getThemeConfig = computed(() => {
   return themeConfig.value;

+ 2 - 2
src/stores/themeConfig.ts

@@ -18,8 +18,8 @@ export const useThemeConfig = defineStore('themeConfig', {
 			 * 全局主题
 			 */
 			// 默认 primary 主题颜色
-			// primary: '#409eff',
-			primary: '#3875F6',
+			primary: '#165DFF',
+			// primary: '#3875F6',
 			// 是否开启深色模式
 			isIsDark: false,
 

+ 154 - 86
src/utils/getStyleSheets.ts

@@ -3,88 +3,150 @@ import * as svg from '@element-plus/icons-vue';
 
 // 获取阿里字体图标
 const getAlicdnIconfont = () => {
-	return new Promise((resolve, reject) => {
-		nextTick(() => {
-			const styles: any = document.styleSheets;
-			let sheetsList = [];
-			let sheetsIconList = [];
-			for (let i = 0; i < styles.length; i++) {
-				if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) {
-					sheetsList.push(styles[i]);
-				}
-			}
-			for (let i = 0; i < sheetsList.length; i++) {
-				for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
-					if (sheetsList[i].cssRules[j].selectorText && sheetsList[i].cssRules[j].selectorText.indexOf('.icon-') > -1) {
-						sheetsIconList.push(
-							`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
-						);
-					}
-				}
-			}
-			if (sheetsIconList.length > 0) resolve(sheetsIconList);
-			else reject('未获取到值,请刷新重试');
-		});
-	});
+  return new Promise((resolve, reject) => {
+    nextTick(() => {
+      const styles: any = document.styleSheets;
+      let sheetsList = [];
+      let sheetsIconList = [];
+      for (let i = 0; i < styles.length; i++) {
+        if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) {
+          sheetsList.push(styles[i]);
+        }
+      }
+      for (let i = 0; i < sheetsList.length; i++) {
+        for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
+          if (sheetsList[i].cssRules[j].selectorText && sheetsList[i].cssRules[j].selectorText.indexOf('.icon-') > -1) {
+            sheetsIconList.push(
+                `${ sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '') }`
+            );
+          }
+        }
+      }
+      if (sheetsIconList.length > 0) resolve(sheetsIconList);
+      else reject('未获取到值,请刷新重试');
+    });
+  });
 };
 
 // 初始化获取 css 样式,获取 element plus 自带 svg 图标,增加了 ele- 前缀,使用时:ele-Aim
 const getElementPlusIconfont = () => {
-	return new Promise((resolve, reject) => {
-		nextTick(() => {
-			const icons = svg as any;
-			const sheetsIconList = [];
-			for (const i in icons) {
-				sheetsIconList.push(`ele-${icons[i].name}`);
-			}
-			if (sheetsIconList.length > 0) resolve(sheetsIconList);
-			else reject('未获取到值,请刷新重试');
-		});
-	});
+  return new Promise((resolve, reject) => {
+    nextTick(() => {
+      const icons = svg as any;
+      const sheetsIconList = [];
+      for (const i in icons) {
+        sheetsIconList.push(`ele-${ icons[i].name }`);
+      }
+      if (sheetsIconList.length > 0) resolve(sheetsIconList);
+      else reject('未获取到值,请刷新重试');
+    });
+  });
 };
 
 // 初始化获取 css 样式,这里使用 fontawesome 的图标
 const getAwesomeIconfont = () => {
-	return new Promise((resolve, reject) => {
-		nextTick(() => {
-			const styles: any = document.styleSheets;
-			let sheetsList = [];
-			let sheetsIconList = [];
-		    // 判断fontFamily是否是本地加载
-			for (let i = 0; i < styles.length; i++) {
-				const rules = styles[i].cssRules || styles[i].rules;
-				if (rules) {
-					for (let j = 0; j < rules.length; j++) {
-						if (rules[j].style && rules[j].style.fontFamily === 'FontAwesome') {
-							sheetsList.push(styles[i])
-						}
-					}
-				}
-			}
-			for (let i = 0; i < styles.length; i++) {
-				if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) {
-					sheetsList.push(styles[i]);
-				}
-			}
-			for (let i = 0; i < sheetsList.length; i++) {
-				for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
-					if (
-						sheetsList[i].cssRules[j].selectorText &&
-						sheetsList[i].cssRules[j].selectorText.indexOf('.fa-') === 0 &&
-						sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1
-					) {
-						if (/::before/.test(sheetsList[i].cssRules[j].selectorText)) {
-							sheetsIconList.push(
-								`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
-							);
-						}
-					}
-				}
-			}
-			if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
-			else reject('未获取到值,请刷新重试');
-		});
-	});
+  return new Promise((resolve, reject) => {
+    nextTick(() => {
+      const styles: any = document.styleSheets;
+      let sheetsList = [];
+      let sheetsIconList = [];
+      // 判断fontFamily是否是本地加载
+      for (let i = 0; i < styles.length; i++) {
+        const rules = styles[i].cssRules || styles[i].rules;
+        if (rules) {
+          for (let j = 0; j < rules.length; j++) {
+            if (rules[j].style && rules[j].style.fontFamily === 'FontAwesome') {
+              sheetsList.push(styles[i]);
+            }
+          }
+        }
+      }
+      for (let i = 0; i < styles.length; i++) {
+        if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) {
+          sheetsList.push(styles[i]);
+        }
+      }
+      for (let i = 0; i < sheetsList.length; i++) {
+        for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
+          if (
+              sheetsList[i].cssRules[j].selectorText &&
+              sheetsList[i].cssRules[j].selectorText.indexOf('.fa-') === 0 &&
+              sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1
+          ) {
+            if (/::before/.test(sheetsList[i].cssRules[j].selectorText)) {
+              sheetsIconList.push(
+                  `${ sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '') }`
+              );
+            }
+          }
+        }
+      }
+      if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
+      else reject('未获取到值,请刷新重试');
+    });
+  });
+};
+
+const getFlagIconfont = () => {
+  return new Promise((resolve, reject) => {
+    nextTick(() => {
+      const styles: any = document.styleSheets;
+      let sheetsList = [];
+      let sheetsIconList = [];
+      for (let i = 0; i < styles.length; i++) {
+        if (styles[i].href && styles[i].href.indexOf('cdn.jsdelivr.net') > -1) {
+          sheetsList.push(styles[i]);
+        }
+      }
+      for (let i = 0; i < sheetsList.length; i++) {
+        for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
+          if (
+              sheetsList[i].cssRules[j].selectorText &&
+              sheetsList[i].cssRules[j].selectorText.indexOf('.fi-') === 0 &&
+              !sheetsList[i].cssRules[j].selectorText.endsWith('.fis')
+          ) {
+            sheetsIconList.push(
+                `${ sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length) }`
+            );
+          }
+        }
+      }
+      if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
+      else reject('未获取到值,请刷新重试');
+    });
+  });
+};
+
+const getBootstrapIconfont = () => {
+  return new Promise((resolve, reject) => {
+    nextTick(() => {
+      const styles: any = document.styleSheets;
+      let sheetsList = [];
+      let sheetsIconList = [];
+      for (let i = 0; i < styles.length; i++) {
+        if (styles[i].href && styles[i].href.indexOf('cdn.jsdelivr.net') > -1) {
+          sheetsList.push(styles[i]);
+        }
+      }
+      for (let i = 0; i < sheetsList.length; i++) {
+        for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
+          if (
+              sheetsList[i].cssRules[j].selectorText &&
+              sheetsList[i].cssRules[j].selectorText.indexOf('.bi-') === 0 &&
+              sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1
+          ) {
+            if (/::before/.test(sheetsList[i].cssRules[j].selectorText)) {
+              sheetsIconList.push(
+                  `${ sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '') }`
+              );
+            }
+          }
+        }
+      }
+      if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
+      else reject('未获取到值,请刷新重试');
+    });
+  });
 };
 
 /**
@@ -94,18 +156,24 @@ const getAwesomeIconfont = () => {
  * @method ali 获取 fontawesome 的图标 `<i class="fa 图标类名"></i>`
  */
 const initIconfont = {
-	// iconfont
-	ali: () => {
-		return getAlicdnIconfont();
-	},
-	// element plus
-	ele: () => {
-		return getElementPlusIconfont();
-	},
-	// fontawesome
-	awe: () => {
-		return getAwesomeIconfont();
-	},
+  // iconfont
+  ali: () => {
+    return getAlicdnIconfont();
+  },
+  // element plus
+  ele: () => {
+    return getElementPlusIconfont();
+  },
+  // fontawesome
+  awe: () => {
+    return getAwesomeIconfont();
+  },
+  flag: () => {
+    return getFlagIconfont();
+  },
+  bootstrap: () => {
+    return getBootstrapIconfont();
+  }
 };
 
 // 导出方法

+ 4 - 1
src/utils/setIconfont.ts

@@ -2,7 +2,10 @@
 const cssCdnUrlList: Array<string> = [
 	'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
 	'//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
-	//'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'
+	// '//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
+	// '//cdn.bootcdn.net/ajax/libs/font-awesome/6.6.0/css/all.css',
+	'//cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css',
+	'//cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css',
 ];
 // 第三方 js url
 const jsCdnUrlList: Array<string> = [];

+ 16 - 0
src/views/product-management/index.vue

@@ -0,0 +1,16 @@
+<script setup lang="ts">
+/**
+ * @Name: index.vue
+ * @Description: 商品管理
+ * @Author: Cheney
+ */
+
+</script>
+
+<template>
+
+</template>
+
+<style scoped>
+
+</style>

+ 84 - 81
src/views/system/menu/index.vue

@@ -1,47 +1,49 @@
 <template>
-	<fs-page>
-		<el-row class="menu-el-row p-5">
-			<el-col :span="6">
-				<div class="menu-box menu-left-box" style="border: 1px solid #DDDFE6">
-					<MenuTreeCom
-						ref="menuTreeRef"
-						:treeData="menuTreeData"
-						@treeClick="handleTreeClick"
-						@updateDept="handleUpdateMenu"
-						@deleteDept="handleDeleteMenu"
-					/>
-				</div>
-			</el-col>
+  <fs-page>
+    <el-row class="menu-el-row p-5">
+      <el-col :span="6">
+        <div class="menu-box menu-left-box" style="border: 1px solid #DDDFE6">
+          <MenuTreeCom
+              ref="menuTreeRef"
+              :treeData="menuTreeData"
+              @deleteDept="handleDeleteMenu"
+              @treeClick="handleTreeClick"
+              @updateDept="handleUpdateMenu"
+          />
+        </div>
+      </el-col>
 
-			<el-col :span="18">
-        <el-tabs type="border-card" style="border-radius: 8px; overflow: hidden;">
-          <el-tab-pane label="按钮权限配置" >
+      <el-col :span="18">
+        <el-tabs style="border-radius: 8px; overflow: hidden;" type="border-card">
+          <el-tab-pane label="按钮权限配置">
             <div style="height: 80vh">
-              <MenuButtonCom ref="menuButtonRef" />
+              <MenuButtonCom ref="menuButtonRef"/>
             </div>
           </el-tab-pane>
           <el-tab-pane label="列权限配置">
             <div style="height: 80vh">
-              <MenuFieldCom ref="menuFieldRef" />
+              <MenuFieldCom ref="menuFieldRef"/>
             </div>
           </el-tab-pane>
         </el-tabs>
-			</el-col>
-		</el-row>
+      </el-col>
+    </el-row>
 
-		<el-drawer v-model="drawerVisible" title="菜单配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
-			<MenuFormCom
-				v-if="drawerVisible"
-				:initFormData="drawerFormData"
-				:cacheData="menuTreeCacheData"
-				:treeData="menuTreeData"
-				@drawerClose="handleDrawerClose"
-			/>
-		</el-drawer>
-	</fs-page>
+    <el-drawer v-model="drawerVisible" :before-close="handleDrawerClose" :close-on-click-modal="false" direction="rtl"
+               size="500px"
+               title="菜单配置">
+      <MenuFormCom
+          v-if="drawerVisible"
+          :cacheData="menuTreeCacheData"
+          :initFormData="drawerFormData"
+          :treeData="menuTreeData"
+          @drawerClose="handleDrawerClose"
+      />
+    </el-drawer>
+  </fs-page>
 </template>
 
-<script lang="ts" setup name="menuPages">
+<script lang="ts" name="menuPages" setup>
 import { ref, onMounted } from 'vue';
 import XEUtils from 'xe-utils';
 import { ElMessageBox } from 'element-plus';
@@ -53,6 +55,7 @@ import { GetList, DelObj } from './api';
 import { successNotification } from '/@/utils/message';
 import { APIResponseData, MenuTreeItemType } from './types';
 
+
 let menuTreeData = ref([]);
 let menuTreeCacheData = ref<MenuTreeItemType[]>([]);
 let drawerVisible = ref(false);
@@ -61,93 +64,93 @@ let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
 let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
 let menuFieldRef = ref<InstanceType<typeof MenuFieldCom> | null>(null);
 const getData = () => {
-	GetList({}).then((ret: APIResponseData) => {
-		const responseData = ret.data;
-		const result = XEUtils.toArrayTree(responseData, {
-			parentKey: 'parent',
-			children: 'children',
-			strict: true,
-		});
-		menuTreeData.value = result;
-	});
+  GetList({}).then((ret: APIResponseData) => {
+    const responseData = ret.data;
+    const result = XEUtils.toArrayTree(responseData, {
+      parentKey: 'parent',
+      children: 'children',
+      strict: true
+    });
+    menuTreeData.value = result;
+  });
 };
 
 /**
  * 菜单的点击事件
  */
 const handleTreeClick = (record: MenuTreeItemType) => {
-	menuButtonRef.value?.handleRefreshTable(record);
-  menuFieldRef.value?.handleRefreshTable(record)
+  menuButtonRef.value?.handleRefreshTable(record);
+  menuFieldRef.value?.handleRefreshTable(record);
 };
 
 /**
  * 部门的 新增 or 编辑 事件
  */
 const handleUpdateMenu = (type: string, record?: MenuTreeItemType) => {
-	if (type === 'update' && record) {
-		const parentData = menuTreeRef.value?.treeRef?.currentNode.parent.data || {};
-		menuTreeCacheData.value = [parentData];
-		drawerFormData.value = record;
-	}
-	drawerVisible.value = true;
+  if (type === 'update' && record) {
+    const parentData = menuTreeRef.value?.treeRef?.currentNode.parent.data || {};
+    menuTreeCacheData.value = [ parentData ];
+    drawerFormData.value = record;
+  }
+  drawerVisible.value = true;
 };
 const handleDrawerClose = (type?: string) => {
-	if (type === 'submit') {
-		getData();
-	}
-	drawerVisible.value = false;
-	drawerFormData.value = {};
+  if (type === 'submit') {
+    getData();
+  }
+  drawerVisible.value = false;
+  drawerFormData.value = {};
 };
 
 /**
  * 部门的删除事件
  */
 const handleDeleteMenu = (id: string, callback: Function) => {
-	ElMessageBox.confirm('您确认删除该菜单项吗?', '温馨提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
-		type: 'warning',
-	}).then(async () => {
-		const res: APIResponseData = await DelObj(id);
-		callback();
-		if (res?.code === 2000) {
-			successNotification(res.msg as string);
-			getData();
-		}
-	});
+  ElMessageBox.confirm('您确认删除该菜单项吗?', '温馨提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(async () => {
+    const res: APIResponseData = await DelObj(id);
+    callback();
+    if (res?.code === 2000) {
+      successNotification(res.msg as string);
+      getData();
+    }
+  });
 };
 
 onMounted(() => {
-	getData();
+  getData();
 });
 </script>
 
 <style lang="scss" scoped>
 .menu-el-row {
-	height: 100%;
-	overflow: hidden;
+  height: 100%;
+  overflow: hidden;
 
-	.el-col {
-		height: 100%;
-		//padding: 20px;
-		box-sizing: border-box;
-	}
+  .el-col {
+    height: 100%;
+    //padding: 20px;
+    box-sizing: border-box;
+  }
 }
 
 .menu-box {
-	height: 100%;
-	padding: 10px;
-	background-color: #fff;
-	box-sizing: border-box;
+  height: 100%;
+  padding: 10px;
+  background-color: #fff;
+  box-sizing: border-box;
 }
 
 .menu-left-box {
-	position: relative;
-	border-radius: 8px;
-	margin-right: 10px;
+  position: relative;
+  border-radius: 8px;
+  margin-right: 10px;
 }
 
 .menu-right-box {
-	border-radius: 8px 0 0 8px;
+  border-radius: 8px 0 0 8px;
 }
 </style>