配置
环境变量
环境变量是运行时动态的全局变量,在项目启动时,会根据当前环境加载对应的环境变量。
Vite 的环境变量命名遵循 VITE_ 开头,然后加上环境名称,比如 VITE_PORT。
配置文件
sunlight-admin 一共有 3 个环境配置文件,具体如下:
├── .env.development # 开发环境
├── .env.production # 生产环境
├── .env.test # 测试环境sunlight-admin 项目使用 .env.* 文件针对不同环境进行配置,每个环境文件对应特定的运行环境:
.env.development:开发环境,当运行pnpm dev时,项目会加载该文件的变量.env.test:测试环境,当运行pnpm build:test时,项目会加载该文件的变量.env.production:生产环境,当运行pnpm build或pnpm build:prod时,项目会加载该文件的变量
提示
在 Vite 中,环境变量的加载顺序为:特定环境文件(.env.development 等)会覆盖默认的环境配置。
基础用法
可以在项目里使用 import.meta.env 获取环境变量。
const { VITE_API_URL, VITE_USER_NODE_ENV } = import.meta.env;
console.log("当前环境变量 VITE_API_URL 为:", VITE_API_URL);
console.log("当前环境类型为:", VITE_USER_NODE_ENV);如果想判断当前项目运行的环境是开发环境还是生产环境,则通过 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 的值了。
配置项
sunlight-admin 支持的环境变量有:
interface ImportMetaEnv {
/**
* 当前环境类型
*/
VITE_USER_NODE_ENV: string;
/**
* 接口基础地址
*/
VITE_API_URL: string;
/**
* 静态资源路径(仅生产环境)
*/
VITE_PUBLIC_PATH?: string;
}环境文件示例
开发环境 (.env.development)
# 开发环境
VITE_USER_NODE_ENV = develop
# 开发环境接口地址
VITE_API_URL = "/"生产环境 (.env.production)
# 生产环境
VITE_USER_NODE_ENV = production
# 公共基础路径
VITE_PUBLIC_PATH = /
# 生产环境接口地址
VITE_API_URL = "https://"测试环境 (.env.test)
# 测试环境
VITE_USER_NODE_ENV = test
# 测试环境接口地址
VITE_API_URL = "/"全局配置文件
sunlight-admin 使用 Pinia 进行全局状态管理,将框架的全局配置统一放到 src/store/modules/global.ts 文件中。
全局状态管理
配置文件
import { defineStore } from 'pinia'
import piniaPersistConfig from '@/plugins/piniaPersist'
export const defaultPrimary: string = '#409eff' // 默认主题颜色
export const useGlobalStore = defineStore({
id: 'app-global',
state: (): GlobalState => ({
title: '网址标题',
// 语言切换
language: 'zh',
// 布局模式 (horizontal | vertical)
layout: 'vertical',
// 主题颜色
primary: defaultPrimary,
// 暗黑模式
isDark: false,
// 折叠
isCollapse: false,
// 面包屑导航
breadcrumb: true,
// 面包屑导航图标
breadcrumbIcon: true,
// 标签页
tabs: true,
// 标签页图标
tabsIcon: true,
// 页脚
footer: true
}),
getters: {},
actions: {
// Set GlobalState
setGlobalState(...args: GlobalType.ObjToKeyValArray<GlobalState>) {
this.$patch({ [args[0]]: args[1] })
}
},
persist: piniaPersistConfig('app-global')
})declare namespace GlobalType {
type LayoutType = 'horizontal' | 'vertical'
type LanguageType = 'zh' | 'en'
/* Generic Tools */
type ObjToKeyValArray<T> = {
[K in keyof T]: [K, T[K]]
}[keyof T]
type ObjToKeyValUnion<T> = {
[K in keyof T]: { key: K; value: T[K] }
}[keyof T]
}
// 全局配置相关接口
interface GlobalState {
title: string
language: GlobalType.LanguageType
layout: GlobalType.LayoutType
primary: string
isDark: boolean
isCollapse: boolean
breadcrumb: boolean
breadcrumbIcon: boolean
tabs: boolean
tabsIcon: boolean
footer: boolean
}路由配置
路由结构
sunlight-admin项目的路由系统采用了静态路由和动态路由分离的设计模式,通过Vue Router实现。
路由文件结构
├── src/router/
│ ├── index.ts # 路由入口文件,包含路由守卫和配置
│ └── modules/
│ └── staticRouter.ts # 静态路由定义静态路由
静态路由是不需要权限验证的路由,包括首页、登录页和错误页面等。
// src/router/modules/staticRouter.ts
export const staticRouter: RouteRecordRaw[] = [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: 'home',
component: () => import('@/views/home/index.vue'),
name: 'Home',
meta: {
title: '首页',
icon: 'menu-home'
}
}
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: { title: '登录', hidden: true }
}
]
// 错误路由
export const errorRouter = [
{ path: '/401', name: '401', component: comp401, meta: { title: '401', hidden: true } },
{ path: '/404', name: '404', component: comp404, meta: { title: '404', hidden: true } },
{ path: '/500', name: '500', component: comp500, meta: { title: '500', hidden: true } },
{ path: '/:pathMatch(.*)*', component: comp404 } // 404路由
]动态路由
动态路由是基于用户角色权限动态生成的路由,需要登录后才能访问。每个动态路由都配置了对应的角色权限,只有拥有相应角色的用户才能访问。
动态路由定义
// src/router/modules/asyncRouter.ts 示例
const Layout = () => import('@/layout/index.vue')
export const asyncRoutes: MenuType.MenuOptions[] = [
{
path: '/project',
name: 'project',
component: Layout,
meta: {
title: '项目列表',
icon: 'menu-project',
roles: ['administrator'], // 只有管理员角色可以访问
isKeepAlive: true
},
children: [
{
path: 'project-test',
component: () => import('@/views/project/index.vue'),
name: 'ProjectTest',
meta: {
title: '项目测试',
icon: 'menu-project',
roles: ['administrator'],
isKeepAlive: true
}
}
]
},
// 更多路由定义...
]动态路由生成
通过authStore.generateAsyncRoutes方法根据用户角色生成可访问的路由:
// src/store/modules/auth.ts 示例
export const useAuthStore = defineStore({
id: 'app-auth',
state: (): AuthState => ({
asyncRoutes: [],
permissions: []
}),
actions: {
// 生成动态路由
generateAsyncRoutes(roles: string[]) {
return new Promise<RouteRecordRaw[]>((resolve) => {
// 过滤出用户角色可访问的路由
let accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
this.asyncRoutes = accessedRoutes
resolve(accessedRoutes)
})
}
}
})路由守卫
路由守卫用于控制路由的访问权限,包括登录验证和权限检查:
// src/router/index.ts
router.beforeEach(async (to, from, next) => {
const globalStore = useGlobalStore()
const authStore = useAuthStore()
const userStore = useUserStore()
NProgress.start()
// 设置页面标题
document.title = `${to.meta.title}` || globalStore.title
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
const hasRoles = userStore.roles && userStore.roles.length > 0
if (hasRoles) {
next()
} else {
try {
const { roles } = await userStore.getInfo()
// 基于角色生成可访问路由数组
const accessRoutes = await authStore.generateAsyncRoutes(roles)
// 动态添加可访问路由
accessRoutes.forEach(item => router.addRoute(item))
next({ ...to, replace: true })
} catch (error) {
await userStore.resetToken()
ElMessage.error(JSON.stringify(error) || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
if (whiteList.includes(to.path)) {
next()
} else {
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})插件配置
Axios请求插件
封装了Axios用于网络请求,包含请求拦截器和响应拦截器:
// src/plugins/request.ts
import axios from 'axios'
import { getToken } from '@/utils/auth'
import { checkStatus } from './request'
const request = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 30 * 1000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}
})
// 请求拦截器
request.interceptors.request.use(
config => {
const token = getToken()
if (token) {
config.headers.Authorization = token
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
response => {
return response.data
},
async error => {
const { response } = error
if (response) checkStatus(response.status)
return Promise.reject(error)
}
)
export default requestNProgress进度条插件
用于页面切换时显示进度条,提升用户体验:
// src/plugins/nprogress.ts
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
// 配置NProgress
NProgress.configure({
easing: 'ease', // 动画方式
speed: 500, // 递增进度条的速度
showSpinner: false, // 是否显示加载指示器
trickleSpeed: 200, // 自动递增间隔
minimum: 0.3 // 初始化时的最小百分比
})
export default NProgressPinia持久化插件
用于将Pinia状态持久化到localStorage中,防止页面刷新后状态丢失:
// src/plugins/piniaPersist.ts
import type { PersistedStateOptions } from 'pinia-plugin-persistedstate'
/**
* @description: 持久化参数配置
* @param {String} key 存储到持久化的name
* @return persist
*/
export const piniaPersistConfig = (key: string): PersistedStateOptions => {
const persist: PersistedStateOptions = {
key,
storage: localStorage,
paths: ['']
}
return persist
}
export default piniaPersistConfig认证配置
sunlight-admin 实现了基于Token的认证系统,用于管理用户登录、权限控制等功能。
Token管理
// src/utils/auth.ts
// 操作token get/set/remove
import Cookies from 'js-cookie'
const TokenKey = 'bk_token' // 自定义修改
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token: any) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}用户状态管理
// src/store/modules/user.ts
import { defineStore } from 'pinia'
import { reactive, toRefs } from 'vue'
import { resetRouter } from '@/router'
import { removeToken } from '@/utils/auth'
import { mockUser } from '@/assets/mock/userMock'
export const useUserStore = defineStore('user', () => {
const initData: UserState = reactive({
token: '',
name: '',
avatar: '', // 头像
introduction: '', // 介绍
roles: [] // 角色
})
// Set Token
function setToken(token: string) {
initData.token = token
}
// remove token
function resetToken() {
initData.token = ''
removeToken() // 清除cookie中token
}
// 获取用户信息
function getInfo() {
return new Promise<any>((resolve): any => {
let info: any = null
const userToken = initData.token.split('-')[0]
info = mockUser[userToken] || {}
const { roles, name, avatar, introduction } = info
initData.name = name
initData.avatar = avatar
initData.introduction = introduction
initData.roles = roles
resolve(info)
})
}
// 退出登录
function logout() {
resetToken()
initData.name = ''
initData.avatar = ''
initData.introduction = ''
initData.roles = []
resetRouter()
}
return {
...toRefs(initData),
setToken,
resetToken,
getInfo,
logout
}
})总结
本文档详细介绍了sunlight-admin项目的配置系统,包括:
- 环境变量配置:使用Vite的环境变量机制,支持开发、测试和生产三种环境
- 全局状态管理:基于Pinia的全局状态管理,包含主题、布局、语言等配置
- 语言配置:使用Vue I18n实现的多语言切换功能
- 主题配置:支持主题颜色自定义和暗黑模式切换的主题系统
- 路由配置:静态路由和动态路由分离的设计模式,支持基于角色的权限控制
- 插件配置:封装了Axios、NProgress和Pinia持久化等常用插件
- 认证配置:基于Token的认证系统,包含用户状态管理和权限控制
通过这些配置,sunlight-admin项目实现了高度的可定制性和扩展性,方便开发者根据实际需求进行调整。项目采用了模块化的设计思想,将不同功能的配置分离到不同的文件中,提高了代码的可维护性和可读性。