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:
parent
503e2d8487
commit
ebb8e808d4
@ -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
2206
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
11
src/common/assets/icons/preserve-color/README.md
Normal file
11
src/common/assets/icons/preserve-color/README.md
Normal 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" />`
|
@ -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>
|
||||
<!-- 内容区放大 -->
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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
6
src/plugins/svg-icon.ts
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
26
types/auto/svg-component-global.d.ts
vendored
Normal 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
26
types/auto/svg-component.d.ts
vendored
Normal 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;
|
||||
}
|
8
types/vue-router.d.ts
vendored
8
types/vue-router.d.ts
vendored
@ -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 的时候该路由不会在侧边栏出现
|
||||
*/
|
||||
|
@ -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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user