chore: 🔨 本地 SVG 加载方式由 vite-plugin-svg-icons 切换为 @unocss/preset-icons

This commit is contained in:
Ray.Hao
2025-02-07 23:57:57 +08:00
parent e041c5575c
commit dd275dae27
23 changed files with 111 additions and 118 deletions

View File

@@ -71,6 +71,7 @@
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@eslint/js": "^9.19.0",
"@iconify/utils": "^2.3.0",
"@types/codemirror": "^5.60.15",
"@types/lodash": "^4.17.15",
"@types/node": "^22.13.1",
@@ -110,7 +111,6 @@
"unplugin-vue-components": "^28.0.0",
"vite": "^6.1.0",
"vite-plugin-mock-dev-server": "^1.8.3",
"vite-plugin-svg-icons": "^2.0.1",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.2.0"
},

View File

@@ -240,7 +240,7 @@
</el-icon>
</template>
<template v-else>
<svg-icon :icon-class="scope.row[col.prop]" />
<div class="i-svg:{{ scope.row[col.prop] }}" />
</template>
</template>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div @click="toggle">
<svg-icon :icon-class="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" />
<div :class="`i-svg:` + (isFullscreen ? 'fullscreen-exit' : 'fullscreen')" />
</div>
</template>

View File

@@ -4,7 +4,7 @@
class="px-[15px] flex items-center justify-center color-[var(--el-text-color-regular)]"
@click="toggleClick"
>
<svg-icon icon-class="collapse" :class="{ hamburger: true, 'is-active': isActive }" />
<div :class="['i-svg:collapse', { hamburger: true, 'is-active': isActive }]" />
</div>
</template>

View File

@@ -11,7 +11,7 @@
<component :is="selectedIcon.replace('el-icon-', '')" />
</el-icon>
<template v-else>
<svg-icon :icon-class="selectedIcon" />
<div :class="`i-svg:${selectedIcon}`" />
</template>
</template>
<template #suffix>
@@ -52,7 +52,7 @@
@click="selectIcon(icon)"
>
<el-tooltip :content="icon" placement="bottom" effect="light">
<svg-icon :icon-class="icon" />
<div :class="`i-svg:${icon}`" />
</el-tooltip>
</li>
</ul>

View File

@@ -1,7 +1,7 @@
<template>
<el-dropdown trigger="click" @command="handleLanguageChange">
<div>
<svg-icon icon-class="language" :size="size" />
<div class="i-svg:language" />
</div>
<template #dropdown>
<el-dropdown-menu>

View File

@@ -1,6 +1,6 @@
<template>
<div @click="openSearchModal">
<svg-icon icon-class="search" />
<div class="i-svg:search" />
<el-dialog
v-model="isModalVisible"
width="30%"
@@ -38,8 +38,8 @@
<el-icon v-if="item.icon && item.icon.startsWith('el-icon')">
<component :is="item.icon.replace('el-icon-', '')" />
</el-icon>
<svg-icon v-else-if="item.icon" :icon-class="item.icon" />
<svg-icon v-else icon-class="menu" />
<div v-else-if="item.icon" :class="`i-svg:${item.icon}`" />
<div v-else class="i-svg:menu" />
{{ item.title }}
</li>
</ul>
@@ -48,14 +48,15 @@
<template #footer>
<div class="dialog-footer">
<svg-icon icon-class="enter" size="20px" />
<div class="i-svg:enter w-5 h-5" />
<span>选择</span>
<svg-icon icon-class="down" size="20px" class="ml-5" />
<svg-icon icon-class="up" size="20px" class="ml-1" />
<div class="i-svg:down w-5 h-5 ml-5" />
<div class="i-svg:up w-5 h-5 ml-5" />
<span>切换</span>
<svg-icon icon-class="esc" size="20px" class="ml-5" />
<div class="i-svg:esc w-5 h-5ml-5" />
<span>退出</span>
</div>
</template>

View File

@@ -3,7 +3,7 @@
<el-tooltip :content="$t('sizeSelect.tooltip')" effect="dark" placement="bottom">
<el-dropdown trigger="click" @command="handleSizeChange">
<div>
<svg-icon icon-class="size" />
<div class="i-svg:size" />
</div>
<template #dropdown>
<el-dropdown-menu>

View File

@@ -1,41 +0,0 @@
<template>
<svg aria-hidden="true" class="svg-icon" :style="'width:' + size + ';height:' + size">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts">
const props = defineProps({
prefix: {
type: String,
default: "icon",
},
iconClass: {
type: String,
required: false,
default: "",
},
color: {
type: String,
default: "",
},
size: {
type: String,
default: "1em",
},
});
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
</script>
<style scoped>
.svg-icon {
display: inline-block;
width: 1em;
height: 1em;
overflow: hidden;
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致而span等标签的下边缘会和字体的基线对齐故需设置一个往下的偏移比例来纠正视觉上的未对齐效果 */
outline: none;
fill: currentcolor; /* 定义元素的颜色currentColor是一个变量这个变量的值就表示当前元素的color值如果当前元素未设置color值则从父元素继承 */
}
</style>

View File

@@ -23,7 +23,7 @@
<!-- 设置面板 -->
<div v-if="defaultSettings.showSettings" @click="settingStore.settingsVisible = true">
<SvgIcon icon-class="setting" />
<div class="i-svg:setting" />
</div>
</div>
</template>

View File

@@ -1,10 +1,13 @@
<template>
<!-- 根据 icon 类型决定使用的不同类型的图标组件 -->
<el-icon v-if="icon && icon.startsWith('el-icon')" class="sub-el-icon">
<component :is="icon.replace('el-icon-', '')" />
</el-icon>
<svg-icon v-else-if="icon" :icon-class="icon" />
<svg-icon v-else icon-class="menu" />
<template v-if="icon">
<el-icon v-if="isElIcon" class="el-icon">
<component :is="iconComponent" />
</el-icon>
<div v-else :class="`i-svg:${icon}`" />
</template>
<template v-else>
<div class="i-svg:menu" />
</template>
<!-- 菜单标题 -->
<span v-if="title" class="ml-1">{{ translateRouteTitle(title) }}</span>
</template>
@@ -12,30 +15,34 @@
<script setup lang="ts">
import { translateRouteTitle } from "@/utils/i18n";
defineProps({
icon: {
type: String,
default: "",
},
title: {
type: String,
default: "",
},
});
const props = defineProps<{
icon?: string;
title?: string;
}>();
const isElIcon = computed(() => props.icon?.startsWith("el-icon"));
const iconComponent = computed(() => props.icon?.replace("el-icon-", ""));
</script>
<style lang="scss" scoped>
.sub-el-icon {
.el-icon {
width: 14px !important;
margin-right: 0 !important;
color: currentcolor;
}
[class^="i-svg:"] {
width: 14px;
height: 14px;
margin-right: 0;
color: currentcolor !important;
}
.hideSidebar {
.el-sub-menu,
.el-menu-item {
.svg-icon,
.sub-el-icon {
.el-icon {
margin-left: 20px;
}
}

View File

@@ -15,7 +15,7 @@
<el-icon v-if="route.meta.icon.startsWith('el-icon')" class="sub-el-icon">
<component :is="route.meta.icon.replace('el-icon-', '')" />
</el-icon>
<svg-icon v-else :icon-class="route.meta.icon" />
<div v-else :class="`i-svg:${route.meta.icon}`" />
</template>
<span v-if="route.path === '/'">首页</span>
<span v-else-if="route.meta && route.meta.title" class="ml-1">

View File

@@ -28,27 +28,27 @@
:style="{ left: left + 'px', top: top + 'px' }"
>
<li @click="refreshSelectedTag(selectedTag)">
<svg-icon icon-class="refresh" />
<div class="i-svg:refresh" />
刷新
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<svg-icon icon-class="close" />
<div class="i-svg:close" />
关闭
</li>
<li @click="closeOtherTags">
<svg-icon icon-class="close_other" />
<div class="i-svg:close_other" />
关闭其它
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<svg-icon icon-class="close_left" />
<div class="i-svg:close_left" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<svg-icon icon-class="close_right" />
<div class="i-svg:close_right" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<svg-icon icon-class="close_all" />
<div class="i-svg:close_all" />
关闭所有
</li>
</ul>

View File

@@ -27,7 +27,7 @@
<Settings v-if="defaultSettings.showSettings" />
<!-- 返回顶部 -->
<el-backtop target=".app-main">
<svg-icon icon-class="backtop" size="24px" />
<div class="i-svg:backtop w-6 h-6" />
</el-backtop>
</div>
</div>
@@ -40,7 +40,7 @@
<Settings v-if="defaultSettings.showSettings" />
<!-- 返回顶部 -->
<el-backtop target=".app-main">
<svg-icon icon-class="backtop" size="24px" />
<div class="i-svg:backtop w-6 h-6" />
</el-backtop>
</div>
</div>

View File

@@ -2,8 +2,6 @@ import { createApp } from "vue";
import App from "./App.vue";
import setupPlugins from "@/plugins";
// 本地SVG图标
import "virtual:svg-icons-register";
// 暗黑主题样式
import "element-plus/theme-chalk/dark/css-vars.css";
// 暗黑模式自定义变量

View File

@@ -86,7 +86,6 @@ declare module "vue" {
SidebarMixTopMenu: (typeof import("./../layout/components/Sidebar/components/SidebarMixTopMenu.vue"))["default"];
SingleImageUpload: (typeof import("./../components/Upload/SingleImageUpload.vue"))["default"];
SizeSelect: (typeof import("./../components/SizeSelect/index.vue"))["default"];
SvgIcon: (typeof import("./../components/SvgIcon/index.vue"))["default"];
TableSelect: (typeof import("./../components/TableSelect/index.vue"))["default"];
TagsView: (typeof import("./../layout/components/TagsView/index.vue"))["default"];
ThemeColorPicker: (typeof import("./../layout/components/Settings/components/ThemeColorPicker.vue"))["default"];

View File

@@ -354,7 +354,7 @@
@node-click="handleFileTreeNodeClick"
>
<template #default="{ data }">
<svg-icon :icon-class="getFileTreeNodeIcon(data.label)" />
<div :class="`i-svg:${getFileTreeNodeIcon(data.label)}`" />
<span class="ml-1">{{ data.label }}</span>
</template>
</el-tree>

View File

@@ -27,15 +27,15 @@
</div>
<div class="mt-3">
<el-link href="https://gitee.com/youlaiorg/vue3-element-admin" target="_blank">
<SvgIcon icon-class="gitee" class="text-lg color-#f76560" />
<div class="i-svg:gitee text-lg color-#F76560" />
</el-link>
<el-divider direction="vertical" />
<el-link href="https://github.com/youlaitech/vue3-element-admin" target="_blank">
<SvgIcon icon-class="github" class="text-lg color-#4080ff" />
<div class="i-svg:github text-lg color-#4080FF" />
</el-link>
<el-divider direction="vertical" />
<el-link href="https://gitcode.com/youlai/vue3-element-admin" target="_blank">
<SvgIcon icon-class="gitcode" class="text-lg color-#ff9a2e" />
<div class="i-svg:gitcode text-lg color-#FF9A2E" />
</el-link>
</div>
</el-col>
@@ -47,18 +47,18 @@
</div>
<div class="mt-3">
<el-link href="https://juejin.cn/post/7228990409909108793" target="_blank">
<SvgIcon icon-class="juejin" class="text-lg" />
<div class="i-svg:juejin text-lg" />
</el-link>
<el-divider direction="vertical" />
<el-link
href="https://youlai.blog.csdn.net/article/details/130191394"
target="_blank"
>
<SvgIcon icon-class="csdn" class="text-lg" />
<div class="i-svg:csdn text-lg" />
</el-link>
<el-divider direction="vertical" />
<el-link href="https://www.cnblogs.com/haoxianrui/p/17331952.html" target="_blank">
<SvgIcon icon-class="cnblogs" class="text-lg" />
<div class="i-svg:cnblogs text-lg" />
</el-link>
</div>
</el-col>
@@ -70,7 +70,7 @@
</div>
<div class="mt-3">
<el-link href="https://www.bilibili.com/video/BV1eFUuYyEFj" target="_blank">
<SvgIcon icon-class="bilibili" class="text-lg" />
<div class="i-svg:bilibili text-lg" />
</el-link>
</div>
</el-col>
@@ -125,7 +125,7 @@
{{ formatGrowthRate(visitStatsData.uvGrowthRate) }}
</span>
</div>
<svg-icon icon-class="visitor" size="2em" />
<div class="i-svg:visitor w-8 h-8" />
</div>
<div class="flex-x-between mt-2 text-sm text-gray">
@@ -181,7 +181,7 @@
{{ formatGrowthRate(visitStatsData.pvGrowthRate) }}
</span>
</div>
<svg-icon icon-class="browser" size="2em" />
<div class="i-svg:browser w-8 h-8" />
</div>
<div class="flex-x-between mt-2 text-sm text-gray">

View File

@@ -7,7 +7,7 @@
<copy-button :text="generateIconCode(item)">
<el-tooltip effect="dark" :content="generateIconCode(item)" placement="top">
<div class="icon-item">
<svg-icon :icon-class="item" />
<div :class="`i-svg:${item}`" />
<span>{{ item }}</span>
</div>
</el-tooltip>
@@ -36,7 +36,6 @@
</template>
<script setup lang="ts">
import SvgIcon from "@/components/SvgIcon/index.vue";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
defineOptions({
@@ -87,7 +86,7 @@ const svg_icons: string[] = [
const icons = ref(ElementPlusIconsVue);
function generateIconCode(symbol: any) {
return `<svg-icon icon-class="${symbol}" />`;
return `<div class="i-svg:${symbol}" />`;
}
function generateElementIconCode(symbol: any) {

View File

@@ -86,7 +86,8 @@
<!-- 验证码 -->
<el-form-item prop="captchaCode">
<div class="input-wrapper">
<svg-icon icon-class="captcha" class="mx-2" />
<div class="i-svg:captcha mx-2" />
<el-input
v-model="loginFormData.captchaCode"
auto-complete="off"
@@ -126,10 +127,10 @@
<el-text size="small">{{ $t("login.otherLoginMethods") }}</el-text>
</el-divider>
<div class="third-party-login">
<svg-icon icon-class="wechat" class="icon" />
<svg-icon icon-class="qq" class="icon" />
<svg-icon icon-class="github" class="icon" />
<svg-icon icon-class="gitee" class="icon" />
<div class="i-svg:wechat" />
<div class="i-svg:qq" />
<div class="i-svg:github" />
<div class="i-svg:gitee" />
</div>
</el-form>
</div>

View File

@@ -48,7 +48,7 @@
</el-icon>
</template>
<template v-else-if="scope.row.icon">
<svg-icon :icon-class="scope.row.icon" />
<div :class="`i-svg:${scope.row.icon}`" />
</template>
{{ scope.row.name }}
</template>

View File

@@ -10,6 +10,25 @@ import {
transformerVariantGroup,
} from "unocss";
import { FileSystemIconLoader } from "@iconify/utils/lib/loader/node-loaders";
import fs from "fs";
const iconsDir = "./src/assets/icons";
// 读取本地 SVG 目录,自动生成 safelist
const generateSafeList = () => {
try {
return fs
.readdirSync(iconsDir)
.filter((file) => file.endsWith(".svg"))
.map((file) => `i-svg:${file.replace(".svg", "")}`);
} catch (error) {
console.error("无法读取图标目录:", error);
return [];
}
};
export default defineConfig({
shortcuts: {
"flex-center": "flex justify-center items-center",
@@ -32,7 +51,22 @@ export default defineConfig({
presets: [
presetUno(),
presetAttributify(),
presetIcons(),
presetIcons({
extraProperties: {
display: "inline-block",
width: "1em",
height: "1em",
},
collections: {
svg: FileSystemIconLoader(iconsDir, (svg) => {
// 如果 `fill` 没有定义,则添加 `fill="currentColor"`
if (!svg.includes('fill="')) {
return svg.replace(/^<svg /, '<svg fill="currentColor" ');
}
return svg;
}),
},
}),
presetTypography(),
presetWebFonts({
fonts: {
@@ -40,5 +74,6 @@ export default defineConfig({
},
}),
],
safelist: generateSafeList(),
transformers: [transformerDirectives(), transformerVariantGroup()],
});

View File

@@ -1,11 +1,10 @@
import vue from "@vitejs/plugin-vue";
import { type UserConfig, type ConfigEnv, loadEnv, defineConfig } from "vite";
import { type ConfigEnv, loadEnv, defineConfig } from "vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import mockDevServerPlugin from "vite-plugin-mock-dev-server";
import UnoCSS from "unocss/vite";
@@ -19,8 +18,10 @@ const __APP_INFO__ = {
};
const pathSrc = resolve(__dirname, "src");
// Vite配置 https://cn.vitejs.dev/config
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
export default defineConfig(({ mode }: ConfigEnv) => {
const env = loadEnv(mode, process.cwd());
return {
resolve: {
@@ -56,9 +57,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
plugins: [
vue(),
env.VITE_MOCK_DEV_SERVER === "true" ? mockDevServerPlugin() : null,
UnoCSS({
hmrTopLevelAwait: false,
}),
UnoCSS(),
// 自动导入配置 https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
AutoImport({
// 导入 Vue 函数ref, reactive, toRef 等
@@ -88,11 +87,6 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
dts: false,
// dts: "src/types/components.d.ts",
}),
createSvgIconsPlugin({
// 缓存图标位置
iconDirs: [resolve(pathSrc, "assets/icons")],
symbolId: "icon-[dir]-[name]",
}),
],
// 预加载项目必需的组件
optimizeDeps: {