配置
环境变量
环境变量是运行时动态的全局变量,在项目启动时,会根据当前环境加载对应的环境变量。
Vite 的环境变量命名遵循 VITE_
开头,然后加上环境名称,比如 VITE_PORT
。
配置文件
Teek 一共有 4
个环境配置文件,具体如下:
├── .env # 基础环境(优先级最低)
├── .env.development # 开发环境
├── .env.production # 生产环境
├── .env.test # 测试环境
.env
是基础环境变量文件,它在每个环境下都生效,因此可以在任意环境下读取里面的变量。
而 .env.*
文件是针对不同环境进行配置的,只能选一个:
.env.development
:开发环境,当运行pnpm dev
时,项目会加载该文件的变量.env.test
:测试环境,当运行pnpm build:test
时,项目会加载该文件的变量.env.production
:生产环境,当运行pnpm build
或pnpm build:prod
时,项目会加载该文件的变量
因此可以将公共的环境变量放在 .env
文件中,而环境变量的差异放在 .env.*
文件中。
提示
如果 .env.*
和 .env
文件都存在同一个环境变量,则 .env.*
文件的优先级更高。
基础用法
可以在项目里使用 import.meta.env
获取环境变量。
const { VITE_PORT } = import.meta.env;
console.log("当前环境变量 VITE_PORT 为:", VITE_PORT);
如果想判断当前项目运行的环境是开发环境还是生产环境,则通过 import.meta.env.MODE
来判断:
if (import.meta.env.MODE === "development") {
console.log("当前环境是开发环境");
} else if (import.meta.env.MODE === "test") {
console.log("当前环境是测试环境");
} else if (import.meta.env.MODE === "production") {
console.log("当前环境是生产环境");
}
当然也可以通过 import.meta.env.DEV
判断是否为开发环境:
if (import.meta.env.DEV) {
console.log("当前环境是开发环境");
}
if (import.meta.env.PROD) {
console.log("当前环境是生产环境");
}
新增环境变量
Vite 规范要求是 VITE_
作为自定义配置前缀,否则无法读取到自定义的配置。
比如要在 .env.production
文件添加一个配置,具体如下
VITE_ENVIRONMENT = private
然后需要加入 TS 类型支持,在 src/types/env.d.ts
的 ImportMetaEnv 里加上 VITE_ENVIRONMENT: string
,具体如下
interface ImportMetaEnv {
/** 环境名称 */
VITE_ENVIRONMENT: string;
}
这样在项目中就可以通过 import.meta.env.VITE_ENVIRONMENT
获取到 VITE_ENVIRONMENT
的值了。
配置项
Teek 支持的环境变量有:
interface ImportMetaEnv {
/**
* 项目端口号
*/
VITE_PORT: number;
/**
* 运行 npm run dev 时自动打开浏览器
*/
VITE_OPEN: boolean;
/**
* 是否生成包分析文件
*/
VITE_REPORT: boolean;
/**
* 是否开启 gzip 压缩
*/
VITE_BUILD_GZIP: boolean;
/**
* 打包过程是否删除 console
*/
VITE_DROP_CONSOLE: boolean;
/**
* 打包过程是否删除 debugger
*/
VITE_DROP_DEBUGGER: boolean;
/**
* 是否生成 sourcemap 文件
*/
VITE_SOURCEMAP: boolean;
/**
* 打包输出目录,默认 dist
*/
VITE_OUT_DIR: string;
/**
* 是否将 css 切割
*/
VITE_CSS_SPLIT: boolean;
/**
* 是否加载所有 element-plus 样式,false 则为按需加载
*/
VITE_LOAD_ALL_EP_STYLE: boolean | string;
/**
* 是否加载所有 element-plus 组件,false 则为按需加载
*/
VITE_LOAD_ALL_EP_COMPONENTS: boolean | string;
/**
* 当前环境
*/
VITE_NODE_ENV: string;
/**
* 接口跟地址
*/
VITE_API_URL: string;
/**
* 静态资源路径
*/
VITE_PUBLIC_PATH: string;
/**
* 打包文件的压缩类型
*/
VITE_COMPRESSION: "none" | "gzip" | "brotli" | "both" | "gzip-clear" | "brotli-clear" | "both-clear";
/**
* 路由模式
*
* @default 'history'
*/
VITE_ROUTER_MODE: string;
/**
* 是否启用 WebSocket
*/
VITE_WEBSOCKET: boolean | string;
/**
* WebSocket 连接 URL,仅当 VITE_WEBSOCKET 为 true 生效
*/
VITE_WEBSOCKET_URL: string;
/**
* 路由权限模式,默认前端 frontend
*/
VITE_ROUTE_ACCESS_MODE: "frontend" | "backend" | "both";
/**
* 是否使用 Vue DevTools 插件
*/
VITE_DEVTOOLS: boolean;
}
全局配置文件
Teek 将框架可以更改的配置统一放到一个文件里,位于 src/common/config/service/base-config.ts
里。
Teek 已经给每个配置添加了默认值:
import type { ServiceConfig } from "./types";
import {
ElementPlusSizeEnum,
HeaderMenuAlignEnum,
HeaderStyleEnum,
LanguageEnum,
LayoutModeEnum,
MenuThemeEnum,
PageTransitionEnum,
GlobalThemeEnum,
TabNavElementModeEnum,
ThemePanelTriggerPositionEnum,
TitleModeEnum,
MenuShowModeEnum,
HeaderShowModeEnum,
MenuStyleEnum,
} from "@/common/enums";
export const defaultServiceConfig: ServiceConfig = {
layout: {
name: "Teek Design Vue3",
avatar: "/avatar.png",
titleMode: TitleModeEnum.ProjectPage,
layoutMode: LayoutModeEnum.Vertical,
maximize: false,
watermark: false,
moreRouteChildrenHideInMenuThenOnlyOne: false,
tooltipEffect: isDark => (isDark ? "light" : "dark"),
elementPlusSize: ElementPlusSizeEnum.Default,
language: LanguageEnum.ZhCn,
watchFrame: false,
lockSecretKey: "my-secret-key",
errorLog: {
showInHeader: true,
printConsole: true,
env: [],
},
themePanelTriggerPosition: ThemePanelTriggerPositionEnum.Header,
},
theme: {
// 默认与 css var 一致,在这里配置一份,方便生成 1 - 9 的基础色
primaryColor: {
[GlobalThemeEnum.System]: "", // 跟随系统即自动获取系统预设的主题色
[GlobalThemeEnum.Light]: "#395ae3",
[GlobalThemeEnum.DarkBlue]: "#4a8fe1",
[GlobalThemeEnum.DarkDeep]: "#4a6bc5",
[GlobalThemeEnum.DarkMidnight]: "#3a7fdb",
[GlobalThemeEnum.DarkNeutral]: "#6d94e6",
},
globalThemeMode: GlobalThemeEnum.System,
defaultDarkMode: GlobalThemeEnum.DarkNeutral,
globalThemeClassName: {
[GlobalThemeEnum.Light]: "",
[GlobalThemeEnum.DarkBlue]: "dark-blue",
[GlobalThemeEnum.DarkDeep]: "dark-deep",
[GlobalThemeEnum.DarkMidnight]: "dark-midnight",
[GlobalThemeEnum.DarkNeutral]: "dark-neutral",
},
radius: 0.75,
weakMode: false,
greyMode: false,
presetsColor: {
[GlobalThemeEnum.Light]: [
"#4a6cf7", // 鲜艳蓝
"#ff6b6b", // 珊瑚粉
"#00bbf9", // 天蓝
"#00f5d4", // 蓝绿
"#708090", // 石板灰
"#f15bb5", // 粉红
"#8ac926", // 黄绿
"#ff9e6b", // 橙红
"#ffd166", // 浅黄
],
[GlobalThemeEnum.DarkBlue]: [
"#5a8fe6", // 亮蓝
"#ff6b6b", // 珊瑚粉
"#1abc9c", // 蓝绿
"#708090", // 石板灰
"#f1c40f", // 明黄
"#2980b9", // 深蓝
"#ff69b4", // 热粉
"#d35400", // 深橙
"#9b59b6", // 紫色
],
[GlobalThemeEnum.DarkDeep]: [
"#db7093", // 深蓝紫
"#8e44ad", // 紫罗兰
"#16a085", // 蓝绿
"#708090", // 石板灰
"#f1c40f", // 明黄
"#2c3e50", // 钢蓝
"#c0392b", // 深红
"#2980b9", // 深蓝
"#27ae60", // 绿松石
],
[GlobalThemeEnum.DarkMidnight]: [
"#ff6b6b", // 珊瑚粉
"#1abc9c", // 蓝绿
"#708090", // 石板灰
"#f1c40f", // 明黄
"#2c3e50", // 钢蓝
"#ff69b4", // 热粉
"#d35400", // 深橙
"#2980b9", // 深蓝
"#27ae60", // 绿松石
],
[GlobalThemeEnum.DarkNeutral]: [
"#ff6b6b", // 珊瑚粉
"#42aaff", // 中性天蓝
"#4cd890", // 中性蓝绿
"#708090", // 石板灰
"#ff9e6b", // 中性橙
"#ff69b4", // 热粉
"#5a7fd9", // 中性蓝紫
"#db7093", // 紫红
"#9b59b6", // 紫色
],
},
},
header: {
enabled: true,
height: 55,
style: HeaderStyleEnum.Page,
menuAlign: HeaderMenuAlignEnum.Start,
showMode: HeaderShowModeEnum.Fixed,
},
menu: {
enabled: true,
width: 240,
accordion: false,
collapsed: false,
collapseWidth: 64,
theme: MenuThemeEnum.Light,
style: MenuStyleEnum.Simple,
showMode: MenuShowModeEnum.Static,
autoActivateChild: true,
showModeAutoFixed: true,
rightClickMenuCollapseToClose: true,
},
tabNav: {
enabled: true,
elementMode: TabNavElementModeEnum.Simple,
showIcon: true,
showDot: true,
persistence: false,
fixed: true,
draggable: true,
height: 38,
middleClickToClose: false,
middleClickToOpen: false,
middleClickToOpenInNewWindow: true,
showMore: true,
wheel: true,
maxCount: 0,
},
breadcrumb: {
enabled: true,
showIcon: true,
hideOnlyOne: false,
showHome: true,
onlyShowHomeIcon: false,
},
logo: {
enable: true,
source: "/logo.png",
},
transition: {
pageEnter: PageTransitionEnum.SlideLeft,
progress: true,
loading: true,
},
widget: {
menuCollapse: true,
refresh: true,
search: true,
fullscreen: true,
notification: true,
language: true,
theme: true,
lockScreen: true,
},
shortcutKey: {
enable: true,
search: true,
logout: true,
lockScreen: true,
},
router: {
whiteList: [""],
routeUseI18n: true,
nameI18nPrefix: "_route",
isKeepAlive: false,
isFull: false,
cacheDynamicRoutes: false,
routeUseTooltip: false,
},
cache: {
cacheKeyPrefix: "teek",
tabNavCacheKey: "tabNav",
cacheDynamicRoutesKey: "dynamicRoutes",
versionCacheKey: "version",
tabExcludesUrlKey: ["layoutMode"],
cleanCacheWhenUpgrade: false,
},
} as ServiceConfig;
import type {
HeaderStyleEnum,
LanguageEnum,
LayoutModeEnum,
ElementPlusSizeEnum,
MenuThemeEnum,
PageTransitionEnum,
GlobalThemeEnum,
TabNavElementModeEnum,
TitleModeEnum,
HeaderMenuAlignEnum,
ThemePanelTriggerPositionEnum,
MenuShowModeEnum,
HeaderShowModeEnum,
MenuStyleEnum,
} from "@/common/enums";
export interface ServiceConfig {
/** 全局配置 */
layout: LayoutConfig;
/** 主题配置 */
theme: ThemeConfig;
/** 顶栏配置 */
header: headerConfig;
/** 菜单栏配置 */
menu: MenuConfig;
/** 标签栏配置 */
tabNav: TabNavConfig;
/** 面包屑配置 */
breadcrumb: BreadcrumbConfig;
/** Logo 配置 */
logo: LogoConfig;
/** 动画配置 */
transition: TransitionConfig;
/** 小部件配置 */
widget: WidgetConfig;
/** 快捷键配置 */
shortcutKey: ShortcutKeyConfig;
/** 路由配置 */
router: RouterConfig;
/** 缓存配置 */
cache: CacheConfig;
}
export interface LayoutConfig {
/** 系统名称 */
name: string;
/** 默认头像 */
avatar: string;
/** 标题在浏览器标签上的多种模式 */
titleMode: TitleModeEnum;
/** 布局设置 */
layoutMode: LayoutModeEnum;
/** PageContent 是否开启最大化,默认不开启(false) */
maximize: boolean;
/** 是否开启水印 */
watermark: boolean;
/** El 组件尺寸 */
elementPlusSize: ElementPlusSizeEnum;
/**
* 这是路由和菜单呼应可能产生的问题而需要配置:alwaysShowRoot 为 false 情况(确保您了解路由的配置规则,如果不了解,前往 router/router-config 查看)
* true:存在多个二级路由,但是只有一个二级路由 hideInMenu 为 false,举例:有 5 个二级路由,但是有 4 个二级路由 hideInMenu: true,则需要开启 true,确保菜单只渲染剩下的路由
*
* 为 true 的场景较少见,如果真的遇到,则开启为 true,否则不建议开启,虽然 true 能无需后顾之忧,但是会多重复一次过滤递归,即消耗点性能
*
* 如果看不懂这个配置没关系,当您配置路由时遇到为 true 的场景时,自然懂得
*/
moreRouteChildrenHideInMenuThenOnlyOne: boolean;
/** 布局的 el-toolTip 风格 */
tooltipEffect: "light" | "dark" | ((isDark: boolean) => "light" | "dark");
/** 国际化 */
language: LanguageEnum;
/** 是否监听 IFrame 传来的通信,用于 Portal 门户系统,来监听门户所有 IFrame 嵌入系统的通信,比如 A 系统想打开 B 系统,则告诉 Portal 门户帮忙打开 */
watchFrame: boolean;
/** 锁屏密钥 */
lockSecretKey: string;
errorLog: {
/** 是否在顶部显示错误日志图标 */
showInHeader: boolean;
/** 是否打印错误日志到控制台 */
printConsole: boolean;
/** 日志收集的环境,对应 .evn.xxx,如 development、test、production */
env: string[];
};
/** 主题面板触发按钮位置 */
themePanelTriggerPosition: ThemePanelTriggerPositionEnum;
}
export interface ThemeConfig {
/** 主题色 */
primaryColor: Partial<Record<GlobalThemeEnum, string>>;
/** 系统主题 */
globalThemeMode: GlobalThemeEnum;
/** 指定当切换为暗色模式(html class 为 dark)或跟随系统时,使用的实际暗色模式 */
defaultDarkMode: GlobalThemeEnum;
/** 不同主题模式在 html 的 className */
globalThemeClassName: Partial<Record<GlobalThemeEnum, string>>;
/** 圆角 */
radius: number;
/** 是否开启灰色主题 */
weakMode: boolean;
/** 是否开启色弱主题 */
greyMode: boolean;
/** 预设颜色 */
presetsColor: Partial<Record<GlobalThemeEnum, string[]>>;
}
export interface headerConfig {
/** 是否使用顶栏 */
enabled: boolean;
/** 顶部高度 */
height: number;
/** 顶部样式 */
style: HeaderStyleEnum;
/** 菜单显示模式 */
showMode: HeaderShowModeEnum;
/** 顶部菜单样式 */
menuAlign: HeaderMenuAlignEnum;
}
export interface MenuConfig {
/** 是否使用菜单栏 */
enabled: boolean;
/** 菜单宽度 */
width: number;
/** 是否开启菜单手风琴 */
accordion: boolean;
/** 是否折叠菜单栏 */
collapsed: boolean;
/** 菜单栏折叠宽度 */
collapseWidth: number;
/** 菜单栏的主题色,暗色和亮色,默认为暗色 */
theme: MenuThemeEnum;
/** 菜单样式,朴素和圆润,默认为朴素 */
style: MenuStyleEnum;
/** 菜单显示模式 */
showMode: MenuShowModeEnum;
/** 点击目录时自动激活子菜单,在分栏布局生效 */
autoActivateChild: boolean;
/** 当菜单显示模式为 Auto 时,是否固定菜单栏 */
showModeAutoFixed: boolean;
/** 鼠标右键点击关闭菜单栏 */
rightClickMenuCollapseToClose: boolean;
}
export interface TabNavConfig {
/** 是否使用 tagsNav */
enabled: boolean;
/** 标签栏元素模式设置 */
elementMode: TabNavElementModeEnum;
/** 标签栏 Icon 是否显示 */
showIcon: boolean;
/** 标签栏 Dot 是否显示,优先级低于 showTabNavDot,仅在 elementMode 为 simple、classic 模式生效 */
showDot: boolean;
/** 是否记录打开过(没关闭)的 tags,下次打开会加载在 tagsNav */
persistence: boolean;
/** 是否固定标签栏 */
fixed: boolean;
/** 是否开启多标签页拖拽 */
draggable: boolean;
/** 标签栏高度 */
height: number;
/** 是否点击中键时关闭标签,优先级低于 middleClickToOpen */
middleClickToClose: boolean;
/** 是否点击中键时打开标签页,优先级低于 middleClickToOpenWindow */
middleClickToOpen: boolean;
/** 是否点击中键时新窗口打开标签页 */
middleClickToOpenInNewWindow: boolean;
/** 显示更多按钮 */
showMore: boolean;
/** 是否开启鼠标滚轮响应 */
wheel: boolean;
/** 最大标签数,超出后关闭最先打开的旧标签,0 表示不限制 */
maxCount: number;
}
export interface BreadcrumbConfig {
/** 是否使用 Breadcrumb */
enabled: boolean;
/** 面包屑 Icon 是否显示 */
showIcon: boolean;
/** 面包屑是否只有一个时隐藏 */
hideOnlyOne: boolean;
/** 面包屑首页面包屑是否可见,当 onlyShowHomeIcon 为 true 则一定为 false */
showHome: boolean;
/** 是否只显示首页图标 */
onlyShowHomeIcon: boolean;
}
export interface LogoConfig {
/** 是否显示 Logo */
enable: boolean;
/** logo 地址 */
source: string;
}
export interface TransitionConfig {
/** 进入页面过渡动画 */
pageEnter: PageTransitionEnum;
/** 是否开启页面加载进度动画 */
progress: boolean;
/** 是否开启页面加载动画 */
loading: boolean;
}
export interface WidgetConfig {
/** 是否显示菜单栏折叠 */
menuCollapse: boolean;
/** 是否显示刷新图标 */
refresh: boolean;
/** 是否显示搜索框 */
search: boolean;
/** 是否显示全屏图标 */
fullscreen: boolean;
/** 是否显示通知图标 */
notification: boolean;
/** 是否显示国际化图标 */
language: boolean;
/** 是否显示暗黑切换图标 */
theme: boolean;
/** 是否显示锁屏按钮 */
lockScreen: boolean;
}
export interface ShortcutKeyConfig {
/** 是否启用快捷键 */
enable: boolean;
/** 是否启用全局搜索快捷键 */
search: boolean;
/** 是否启用全局注销快捷键 */
logout: boolean;
/** 是否启用全局锁屏快捷键 */
lockScreen: boolean;
}
export interface RouterConfig {
/**
* 白名单额三种模式:["*"]、["next"]、[to.path, ...]
* '*' 代表加载所有路由;next 代表直接放行,但不加载权限路由;to.path 表示指定的路由可以放行,可以填多个
* 优先级:* > next > to.path
*/
whiteList: string[];
/** 「路由」布局是否使用国际化,默认为 false,如果不使用,则需要在路由中给需要在菜单中展示的路由设置 meta: {title: 'xxx'} 用来在菜单中显示文字 */
routeUseI18n: boolean;
/** 当使用路由国际化时,通过 name 属性读取国际化文本的前缀 */
nameI18nPrefix: string;
/** 路由是否开启缓存 */
isKeepAlive: boolean;
/** 是否全屏,不渲染 Layout 布局,只渲染当前路由组件 */
isFull: boolean;
/** 是否缓存路由,默认不开启(false) */
cacheDynamicRoutes: boolean;
/** 菜单的文字超出后,是否使用 el-toolTip 提示,仅针二级路由及以上生效 */
routeUseTooltip: boolean;
}
export interface CacheConfig {
/** 缓存 key 前缀 */
cacheKeyPrefix: string;
/** 缓存标签栏的 key */
tabNavCacheKey: string;
/** 缓存版本号的 key */
versionCacheKey: string;
/** 当 URL 携带参数时,标签栏会出现多个重复且名字一样的 tab,该配置可指定忽略哪些参数生成新的 tab,如果为 * 则忽略所有参数 */
tabExcludesUrlKey: string[];
/** 缓存路由的 key */
cacheDynamicRoutesKey: string;
/** 是否在升级时清理所有缓存,默认 false */
cleanCacheWhenUpgrade: boolean;
}
基础用法
引入 serviceConfig:
import { serviceConfig } from "@/common/config";
console.log(serviceConfig.layout.name);
配置新增
举个例子,当想添加的自定义配置为 isKeepAlive
,boolean
类型:
首先加入类型支持,在 src/common/config/service/types.ts
的 RouterConfig
里添加 isKeepAlive: boolean
。
export interface RouterConfig {
/** 路由是否开启缓存 */
isKeepAlive?: boolean;
}
然后根据在 config/service/service/base-config.ts
里加上默认值:
const defaultServiceConfig: ServiceConfig = {
router: {
isKeepAlive: false,
},
};
最后在想要使用的文件引入并使用:
import SystemConfig from "@/common/config";
console.log(SystemConfig.routerConfig.isKeepAlive);
默认配置修改
直接在 src/common/config/service/base-config.ts
文件里修改默认配置即可。
环境变量配置
除了直接修改默认的配置,也可以单独给某个环境配置,比如 development
环境下,给 router
配置 isKeepAlive
为 true
,然后在 prod
环境配置下给 router
配置 isKeepAlive
为 false
。
可以在 src/common/config/service/env-config.ts
文件下添加,格式与默认配置一致:
import type { ServiceConfig } from "./types";
/**
* 根据环境变量创建配置
*/
export const defineEnvServiceConfig = (): DeepPartial<ServiceConfig> => {
const isDev = import.meta.env.MODE === "development";
const isTest = import.meta.env.MODE === "test";
const isProd = import.meta.env.MODE === "production";
// 本地环境
if (isDev) {
return {
router: {
isKeepAlive: true,
},
} as DeepPartial<ServiceConfig>;
}
// 测试环境
if (isTest) {
return {} as DeepPartial<ServiceConfig>;
}
// 生产环境
if (isProd) {
return {
router: {
isKeepAlive: false,
},
} as DeepPartial<ServiceConfig>;
}
return {};
};
覆盖默认配置
除了直接修改默认配置文件,也可以在 src/common/config/service/index.ts
文件的 overrideServiceConfig
属性里填写配置信息,Teek 在生成配置的时候,会将该属性覆盖在默认配置之上。
import type { ServiceConfig } from "./types";
import { defineServiceConfig } from "./override-config";
export { defaultServiceConfig } from "./base-config";
// 可以在这里覆盖框架默认的配置
const overrideServiceConfig: DeepPartial<ServiceConfig> = {
router: {
isKeepAlive: false,
},
};
// 冻结对象防止运行时修改
export const serviceConfig = Object.freeze(defineServiceConfig(overrideServiceConfig));
为什么提供覆盖默认配置而不是提倡直接修改默认配置?
Teek 后续计划提供 monorepo
版,因此现在的设计都会考虑兼容 monorepo
场景,在 monorepo
模式下,serviceConfig
会被多个项目共享,因此 Teek 默认的配置不建议修改,而是每个项目通过覆盖的方式来生成自己独有的配置。
自定义命名空间
Teek 内置组件的 class 名称都以 tk-
开头,在特殊情况下,我们需要自定义命名空间。
在 src/common/style/mixins/namespace.scss
中可以修改命名空间。
除此之外也可以在该文件修改 Element Plus 的命名空间。
提示
Teek 的命名空间不建议和 Element Plus 的命名空间命名一致,防止 Element Plus 和 Teek 的样式冲突。