Template
1
0
mirror of https://github.com/un-pany/v3-admin-vite.git synced 2025-04-20 10:59:21 +08:00

feat: 引入 unplugin-svg-component 插件代替 vite-plugin-svg-icons 插件 & elIcon 支持类型提示 (#220)

Co-authored-by: pany <panyang@mafengwo.com>
This commit is contained in:
ClariS 2024-12-02 17:07:21 +08:00 committed by GitHub
parent 503e2d8487
commit ebb8e808d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 583 additions and 1781 deletions

View File

@ -50,9 +50,9 @@
"typescript": "5.6.3",
"unocss": "0.64.1",
"unplugin-auto-import": "0.18.6",
"unplugin-svg-component": "0.10.4",
"unplugin-vue-components": "0.27.5",
"vite": "6.0.1",
"vite-plugin-svg-icons": "2.0.1",
"vite-svg-loader": "5.1.0",
"vitest": "2.1.6",
"vue-tsc": "2.1.10"

2206
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
## 目录说明
- `common/assets/icons/preserve-color` 目录下存放带颜色的 svg icon
- `common/assets/icons` 目录存放的 svg icon 会被插件重写 `fill``stroke` 属性,使得图片自带的颜色丢失,从而继承父元素的颜色
## 使用说明
`common/assets/icons/preserve-color` 目录下需要添加 `preserve-color/` 前缀,像这样: `<SvgIcon name="preserve-color/name" />`
`common/assets/icons` 目录下则不需要,像这样: `<SvgIcon name="name" />`

View File

@ -80,11 +80,11 @@ function handleContentFullClick() {
<div>
<!-- 全屏 -->
<el-tooltip v-if="!props.content" effect="dark" :content="fullscreenTips" placement="bottom">
<SvgIcon :name="fullscreenSvgName" @click="handleFullscreenClick" />
<SvgIcon :name="fullscreenSvgName" @click="handleFullscreenClick" class="svg-icon" />
</el-tooltip>
<!-- 内容区 -->
<el-dropdown v-else :disabled="isFullscreen">
<SvgIcon :name="contentLargeSvgName" />
<SvgIcon :name="contentLargeSvgName" class="svg-icon" />
<template #dropdown>
<el-dropdown-menu>
<!-- 内容区放大 -->

View File

@ -14,16 +14,16 @@ const { isMobile } = useDevice()
<div class="search-footer">
<template v-if="!isMobile">
<span class="search-footer-item">
<SvgIcon name="keyboard-enter" />
<SvgIcon name="keyboard-enter" class="svg-icon" />
<span>确认</span>
</span>
<span class="search-footer-item">
<SvgIcon name="keyboard-up" />
<SvgIcon name="keyboard-down" />
<SvgIcon name="keyboard-up" class="svg-icon" />
<SvgIcon name="keyboard-down" class="svg-icon" />
<span>切换</span>
</span>
<span class="search-footer-item">
<SvgIcon name="keyboard-esc" />
<SvgIcon name="keyboard-esc" class="svg-icon" />
<span>关闭</span>
</span>
</template>

View File

@ -155,7 +155,7 @@ function handleReleaseUpOrDown() {
>
<el-input ref="inputRef" v-model="keyword" placeholder="搜索菜单" size="large" clearable @input="handleSearch">
<template #prefix>
<SvgIcon name="search" />
<SvgIcon name="search" class="svg-icon" />
</template>
</el-input>
<el-empty v-if="result.length === 0" description="暂无搜索结果" :image-size="100" />

View File

@ -76,12 +76,12 @@ defineExpose({ getScrollTop })
:style="itemStyle(item)"
@mouseenter="handleMouseenter(item)"
>
<SvgIcon v-if="item.meta?.svgIcon" :name="item.meta.svgIcon" />
<SvgIcon v-if="item.meta?.svgIcon" :name="item.meta.svgIcon" class="svg-icon" />
<component v-else-if="item.meta?.elIcon" :is="item.meta.elIcon" class="el-icon" />
<span class="result-item-title">
{{ item.meta?.title }}
</span>
<SvgIcon v-if="modelValue && modelValue === item.name" name="keyboard-enter" />
<SvgIcon v-if="modelValue && modelValue === item.name" name="keyboard-enter" class="svg-icon" />
</div>
</div>
</template>

View File

@ -13,7 +13,7 @@ function handleOpen() {
<template>
<div>
<el-tooltip effect="dark" content="搜索菜单" placement="bottom">
<SvgIcon name="search" @click="handleOpen" />
<SvgIcon name="search" @click="handleOpen" class="svg-icon" />
</el-tooltip>
<Modal v-model="visible" />
</div>

View File

@ -1,27 +0,0 @@
<script lang="ts" setup>
interface Props {
prefix?: string
name: string
}
const props = withDefaults(defineProps<Props>(), {
prefix: "icon"
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>
<template>
<svg class="svg-icon">
<use :href="symbolId" />
</svg>
</template>
<style lang="scss" scoped>
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@ -52,7 +52,7 @@ function resolvePath(routePath: string) {
<template v-if="!alwaysShowRootMenu && theOnlyOneChild && !theOnlyOneChild.children">
<Link v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
<el-menu-item :index="resolvePath(theOnlyOneChild.path)">
<SvgIcon v-if="theOnlyOneChild.meta.svgIcon" :name="theOnlyOneChild.meta.svgIcon" />
<SvgIcon v-if="theOnlyOneChild.meta.svgIcon" :name="theOnlyOneChild.meta.svgIcon" class="svg-icon" />
<component v-else-if="theOnlyOneChild.meta.elIcon" :is="theOnlyOneChild.meta.elIcon" class="el-icon" />
<template v-if="theOnlyOneChild.meta.title" #title>
<span class="title">{{ theOnlyOneChild.meta.title }}</span>
@ -62,7 +62,7 @@ function resolvePath(routePath: string) {
</template>
<el-sub-menu v-else :index="resolvePath(props.item.path)" teleported>
<template #title>
<SvgIcon v-if="props.item.meta?.svgIcon" :name="props.item.meta.svgIcon" />
<SvgIcon v-if="props.item.meta?.svgIcon" :name="props.item.meta.svgIcon" class="svg-icon" />
<component v-else-if="props.item.meta?.elIcon" :is="props.item.meta.elIcon" class="el-icon" />
<span v-if="props.item.meta?.title" class="title">{{ props.item.meta.title }}</span>
</template>

View File

@ -1,12 +1,12 @@
import type { App } from "vue"
import { installElementPlusIcons } from "./element-plus-icons"
import { installPermissionDirective } from "./permission-directive"
import { installSvgIcons } from "./svg-icons"
import { installSvgIcon } from "./svg-icon"
import { installVxeTable } from "./vxe-table"
export function installPlugins(app: App) {
installElementPlusIcons(app)
installPermissionDirective(app)
installSvgIcons(app)
installSvgIcon(app)
installVxeTable(app)
}

6
src/plugins/svg-icon.ts Normal file
View File

@ -0,0 +1,6 @@
import type { App } from "vue"
import SvgIcon from "~virtual/svg-component"
export function installSvgIcon(app: App) {
app.component("SvgIcon", SvgIcon)
}

View File

@ -1,7 +0,0 @@
import type { App } from "vue"
import SvgIcon from "@@/components/SvgIcon/index.vue" // Svg Component
import "virtual:svg-icons-register"
export function installSvgIcons(app: App) {
app.component("SvgIcon", SvgIcon)
}

View File

@ -160,7 +160,7 @@ export const constantRoutes: RouteRecordRaw[] = [
path: "/link",
meta: {
title: "文档链接",
elIcon: "link"
elIcon: "Link"
},
children: [
{
@ -196,7 +196,7 @@ export const dynamicRoutes: RouteRecordRaw[] = [
name: "Permission",
meta: {
title: "权限演示",
elIcon: "lock",
elIcon: "Lock",
// 可以在根路由中设置角色
roles: ["admin", "editor"],
alwaysShow: true

26
types/auto/svg-component-global.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
/* eslint-disable */
/* prettier-ignore */
// biome-ignore format: off
// biome-ignore lint: off
// @ts-nocheck
// Generated by unplugin-svg-component
import 'vue'
declare module 'vue' {
export interface GlobalComponents {
SvgIcon: import("vue").DefineComponent<{
name: {
type: import("vue").PropType<"dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search">;
default: string;
required: true;
};
}, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
name: {
type: import("vue").PropType<"dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search">;
default: string;
required: true;
};
}>>, {
name: "dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search";
}>;
}
}

26
types/auto/svg-component.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
/* eslint-disable */
/* prettier-ignore */
// biome-ignore format: off
// biome-ignore lint: off
// @ts-nocheck
// Generated by unplugin-svg-component
declare module '~virtual/svg-component' {
const SvgIcon: import("vue").DefineComponent<{
name: {
type: import("vue").PropType<"dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search">;
default: string;
required: true;
};
}, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
name: {
type: import("vue").PropType<"dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search">;
default: string;
required: true;
};
}>>, {
name: "dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search";
}>;
export const svgNames: ["dashboard", "fullscreen-exit", "fullscreen", "keyboard-down", "keyboard-enter", "keyboard-esc", "keyboard-up", "search"];
export type SvgName = "dashboard" | "fullscreen-exit" | "fullscreen" | "keyboard-down" | "keyboard-enter" | "keyboard-esc" | "keyboard-up" | "search";
export default SvgIcon;
}

View File

@ -1,7 +1,11 @@
import type * as ElementPlusIconsVue from "@element-plus/icons-vue"
import type { SvgName } from "~virtual/svg-component"
import "vue-router"
export {}
type ElementPlusIconsName = keyof typeof ElementPlusIconsVue
declare module "vue-router" {
interface RouteMeta {
/**
@ -11,11 +15,11 @@ declare module "vue-router" {
/**
* @description svg src/common/assets/icons
*/
svgIcon?: string
svgIcon?: SvgName
/**
* @description 使 Element Plus Icon svgIcon svgIcon
*/
elIcon?: string
elIcon?: ElementPlusIconsName
/**
* @description false true
*/

View File

@ -5,10 +5,10 @@ import vue from "@vitejs/plugin-vue"
import vueJsx from "@vitejs/plugin-vue-jsx"
import UnoCSS from "unocss/vite"
import AutoImport from "unplugin-auto-import/vite"
import UnpluginSvgComponent from "unplugin-svg-component/vite"
import { ElementPlusResolver } from "unplugin-vue-components/resolvers"
import Components from "unplugin-vue-components/vite"
import { defineConfig, loadEnv } from "vite"
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
import svgLoader from "vite-svg-loader"
// Configuring Vite: https://cn.vite.dev/config
@ -94,10 +94,13 @@ export default defineConfig(({ mode }) => {
vueJsx(),
// 将 SVG 文件转化为 Vue 组件
svgLoader({ defaultImport: "url" }),
// 生成 SVG 雪碧图
createSvgIconsPlugin({
iconDirs: [resolve(root, "src/common/assets/icons")],
symbolId: "icon-[dir]-[name]"
// 自动生成 SvgIcon 组件和 SVG 雪碧图
UnpluginSvgComponent({
iconDir: [resolve(__dirname, "src/common/assets/icons")],
preserveColor: resolve(__dirname, "src/common/assets/icons/preserve-color"),
dts: true,
dtsDir: resolve(__dirname, "types/auto"),
treeShaking: false
}),
// 原子化 CSS
UnoCSS(),