本文涉及到的内容
- 如何配置项目别名
- 如何配置环境变量
- 集成element-plus和自定义Svg图标
- 集成vue-router
- 集成pinia,使用Pinia管理用户信息
- 集成axios
- 集成Mock,如何Mock用户相关的信息,如何使用Token做用户鉴权
- 如何进行全局组件的注册
本文接上期文章# vue3 admin 保姆教学指南 | 项目规范集成教程,看完秒懂项目中各种奇怪的文件和配置,在此基础上迭代。
不废话,直接上干货。
集成Element-plus
1.安装Element Plus和图标组件
pnpm install element-plus @element-plus/icons-vue
2.全局注册组件
// main.ts
import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/index.css'
createApp(App)
.use(ElementPlus)
.mount('#app')
23Element Plus全局组件类型声明
// tsconfig.json
{
"compilerOptions": {
// ...
"types": ["element-plus/global"]
}
}
4.页面使用 Element Plus 组件和图标
<!-- src/App.vue -->
<template>
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite"/>
<div style="text-align: center;margin-top: 10px">
<el-button :icon="Search" circle></el-button>
<el-button type="primary" :icon="Edit" circle></el-button>
<el-button type="success" :icon="Check" circle></el-button>
<el-button type="info" :icon="Message" circle></el-button>
<el-button type="warning" :icon="Star" circle></el-button>
<el-button type="danger" :icon="Delete" circle></el-button>
</div>
</template>
<script lang="ts" setup>
import HelloWorld from '/src/components/HelloWorld.vue'
import {Search, Edit,Check,Message,Star, Delete} from '@element-plus/icons-vue'
</script>
路径别名配置
使用 @ 代替 src
1. Vite配置
// vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
}
}
})
引用path的时候会报类型错误,记得pnpm add -D @types/node
,安装完以后会在多一个文件tsconfig.node.json
2. TypeScript 编译配置
因为 typescript 特殊的 import 方式 , 需要配置允许默认导入的方式,还有路径别名的配置
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
},
"allowSyntheticDefaultImports": true // 允许默认导入
}
}
4.别名使用
// App.vue
import HelloWorld from '/src/components/HelloWorld.vue'
↓
import HelloWorld from '@/components/HelloWorld.vue'
可以直接cmd+鼠标左键跳转到对应的文件目录。
如果遇到无法导入的情况,重启一下vscode
环境变量配置
1. env配置文件
项目根目录分别添加 开发、生产和模拟环境配置文件,如下图所示
文件内容
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
VITE_APP_TITLE = '尚品汇商城后台管理系统'
VITE_APP_PORT = 3002
VITE_APP_BASE_API = '/api'
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'production'
VITE_APP_TITLE = '尚品汇商城后台管理系统'
VITE_APP_PORT = 3002
VITE_APP_BASE_API = '/prod-api'
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'test'
VITE_APP_TITLE = '尚品汇商城后台管理系统'
VITE_APP_PORT = 3002
VITE_APP_BASE_API = '/test-api'
默认我们运行pnpm dev
的时候NDOE_ENV='development'
,运行pnpm build
的时候NODE_ENV='production'
,多了一个test环境以后,我们就需要上面的方式额外添加一个test环境变量
配置运行命令
"scripts": {
"dev": "vite", // dev环境不需要添加 --mode,默认就是 development
"build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production",
}
获取NODE_ENV
获取环境变量可以通过process.env.NODE_ENV
来获取,后面我们就可以这个变量来区分不同环境了。
获取其他环境变量
import { defineConfig, loadEnv } from 'vite'
export default defineConfig((config) => {
// 根据当前工作目录中的 `mode` 加载 .env 文件
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
const { command, mode } = config
const env = loadEnv(mode, process.cwd(), '')
console.log(env.VITE_APP_TITLE)
})
通过loadEnv()
函数可以获取配置文件中的参数
SVG图标配置
安装依赖
pnpm install vite-plugin-svg-icons -D
使用
在vite.config.ts
中配置插件
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
return {
plugins: [
createSvgIconsPlugin({
// Specify the icon folder to be cached
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// Specify symbolId format
symbolId: 'icon-[dir]-[name]',
}),
],
}
}
main.ts
导入
import 'virtual:svg-icons-register'
封装/src/components/SvgIcon.vue
组件
<template>
<svg
aria-hidden="true"
:class="['svg-icon', spin && 'svg-icon-spin']"
:style="getStyle"
>
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { CSSProperties } from 'vue'
const props = defineProps({
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
color: {
type: String,
default: '',
},
size: {
type: [Number, String],
default: 20,
},
spin: {
type: Boolean,
default: false,
},
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const getStyle = computed((): CSSProperties => {
const { size } = props
let s = `${size}`
s = `${s.replace('px', '')}px`
return {
width: s,
height: s,
}
})
</script>
<style scoped>
.svg-icon {
display: inline-block;
overflow: hidden;
vertical-align: -0.15em;
fill: currentColor;
}
.svg-icon-spin {
animation: loadingCircle 1s infinite linear;
}
/* 旋转动画 */
@keyframes loadingCircle {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
上面我们定义的Svg组件可以配置以下功能:
- 名称
- 大小
- 颜色
- 是否loading效果
我们求阿里巴巴图标库下载一个icon,如下图:
找一个喜欢的图标,然后点击复制SVG代码。
在项目目录src/assets/icon
下面创建一个refresh.svg
文件,然后把刚才复制的代码粘贴到里面。
使用
<template>
<div>
<div>
<svg-icon name="refresh" spin></svg-icon>
</div>
</div>
</template>
<script setup lang="ts">
import SvgIcon from '@/components/SvgIcon/index.vue'
</script>
效果如下:
你可以根据自己的需求更改它的大小或者颜色,是否让它旋转。而且我们使用SVG以后,页面上加载的不再是图片资源,而是下面这样的:
这对页面性能来说是个很大的提升,而且我们SVG文件比img要小的很多,放在项目中几乎不占用资源。
全局注册组件
上面的SvgIcon组件我们在使用的都需要手动的引入一下,如果我们自定义的组件很多的时候,这样就显得很不方便了,所以我们来把上面我们的组件改造一下,使用全局注册的方式来。
定义组件改造,我们把组件目录修改成/components/SvgIcon/src/SvgIcon.vue
在/components/SvgIcon
下新建一个index.ts
文件,暴露出组件
import SvgIcon from './src/SvgIcon.vue'
export { SvgIcon }
在components
下新建index.ts
文件,用来把所有的组件引入,然后提供一个install方法
import type { App, Component } from 'vue'
// 当组件很多的时候,可以使用
import { SvgIcon } from './SvgIcon'
// 这个地方
const Components: {
[propName: string]: Component
} = { SvgIcon }
export default {
install: (app: App) => {
Object.keys(Components).forEach((key) => {
app.component(key, Components[key])
})
},
}
install是专门用来提供安装插件的一个方法,这样我们就可以使用app.use()用来注册所有的全局组件了。
在main.ts
中
import { createApp } from 'vue'
import './style.less'
import App from './App.vue'
import registerGlobComp from '@/components'
const app = createApp(App)
app.use(registerGlobComp)
app.mount('#app')
这样我们在使用SvgIcon
组件的时候,就不用再引入一次了
<template>
<svg-icon name="refresh" spin></svg-icon>
</template>
<script setup lang="ts">
// import SvgIcon from '@/components/SvgIcon/src/SvgIcon.vue'
</script>
<style scoped lang="less">
</style>
集成Mock
1.安装依赖
pnpm add -D vite-plugin-mock mockjs
2.在 vite.config.js 配置文件启用插件。
Mock 服务通常只用于开发阶段,因此我们需要在配置文件中判断当前所处环境。
在 webpack 中通常会配置一个 NODE_ENV 的环境变量。而在 Vite 中,不用开发者进行设置,它提供了一种方便的判断开发环境和生产环境的方式,如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig((config) => {
const { command } = config
return {
plugins: [
vue(),
viteMockServe({
// 只在开发阶段开启 mock 服务
localEnabled: command === 'serve'
})
]
}
})
dev环境下command='serve'
,build环境下command='build'
3.创建API
在根目录创建mock
文件夹,然后创建user.ts
文件,添加用户相关的接口
import { resultError, resultSuccess, getRequestToken } from './_utils'
// mock/user.ts
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平台管理员',
roles: ['平台管理员'],
buttons: ['cuser.detail'],
routes: ['home'],
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'system',
password: '111111',
desc: '系统管理员',
roles: ['系统管理员'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['home'],
token: 'System Token',
},
]
}
export default [
// 用户登录
{
url: '/api/user/login',
method: 'post',
response: ({ body }) => {
const { username, password } = body
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
if (!checkUser) {
return resultError('Incorrect username or password!')
}
const { token } = checkUser
return resultSuccess({
token,
})
},
},
// 获取用户信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
const token = getRequestToken(request)
console.log(token)
const checkUser = createUserList().find((item) => item.token === token)
if (!checkUser) {
return resultError(
'The corresponding user information was not obtained!',
)
}
return resultSuccess(checkUser)
},
},
]
上面我们写了两个接口,第一个是用户登陆接口,接收username
和password
参数,然后在createUserList()
做匹配,返回给前端。第二个是获取用户信息接口,接收token,然后从headers从拿到token信息,再从createUserList()
做匹配,返回给前端。
然后我们就可以直接跟正常请求api一样,去请求对应的接口了
接下来我们封装一下axios,然后测试我们的mock接口
集成Axios
我们的axios封装放在目录src/utils/http
下面,创建一个index.ts
文件
import axios from 'axios'
import type {
AxiosInstance,
AxiosError,
AxiosRequestConfig,
AxiosResponse,
} from 'axios'
import { ElMessage } from 'element-plus'
import { localGet } from '../cache'
import { TOKEN_KEY } from '../../enums/cacheEnum'
const service: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 0,
})
/* 请求拦截器 */
service.interceptors.request.use(
(config) => {
const token = localGet(TOKEN_KEY)
if (token) {
config.headers.Authorization = `${token}`
}
return config
},
(error: AxiosError) => {
ElMessage.error(error.message)
return Promise.reject(error)
},
)
/* 响应拦截器 */
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, message, data } = response.data
// 根据自定义错误码判断请求是否成功
if (code === 0) {
// 将组件用的数据返回
return data
} else {
// 处理业务错误。
ElMessage.error(message)
return Promise.reject(new Error(message))
}
},
(error: AxiosError) => {
// 处理 HTTP 网络错误
let message = ''
// HTTP 状态码
const status = error.response?.status
switch (status) {
case 401:
message = 'token 失效,请重新登录'
// 这里可以触发退出的 action
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址错误'
break
case 500:
message = '服务器故障'
break
default:
message = '网络连接故障'
}
ElMessage.error(message)
return Promise.reject(error)
},
)
/* 导出封装的请求方法 */
const http = {
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return service.get(url, config)
},
post<T = any>(
url: string,
data?: object,
config?: AxiosRequestConfig,
): Promise<T> {
return service.post(url, data, config)
},
put<T = any>(
url: string,
data?: object,
config?: AxiosRequestConfig,
): Promise<T> {
return service.put(url, data, config)
},
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return service.delete(url, config)
},
}
export default http
集成Pinia
安装依赖
pnpm add pinia
引入pinia
创建文件store/index.ts
,添加如下内容
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
然后在main.ts
中使用一下
import { createApp } from 'vue'
import './style.less'
import App from './App.vue'
import pinia from '@/store'
createApp(App).use(pinia).mount('#app')
封装userState信息
import { defineStore } from 'pinia'
import { login, getUserInfo } from '@/api'
import { LoginParams } from './model/userModel'
import { localSet, localGet } from '@/utils/cache'
import { TOKEN_KEY, USER_INFO_KEY } from '@/enums/cacheEnum'
import type { UserState } from './model/userModel'
import type { UserInfo } from '@/types/store'
export const useUserStore = defineStore({
id: 'app-user',
state: (): UserState => ({
userInfo: null,
token: undefined,
}),
getters: {
getUserInfo(): UserInfo {
return (this.userInfo as UserInfo) || localGet(USER_INFO_KEY) || {}
},
getToken(): string {
return (this.token as string) || localGet(TOKEN_KEY) || ''
},
},
actions: {
setToken(token: string | undefined) {
this.token = token ? token : ''
localSet(TOKEN_KEY, token)
},
setUserInfo(info: UserInfo) {
this.userInfo = info
localSet(USER_INFO_KEY, info)
},
async login(params: LoginParams) {
try {
const data = await login(params)
const { token } = data
this.setToken(token)
this.getUserInfoAction()
} catch (error) {
return Promise.reject(error)
}
},
async getUserInfoAction() {
try {
const data = await getUserInfo()
this.setUserInfo(data)
} catch (error) {
return Promise.reject(error)
}
},
},
})
这里我们使用pinia对用户信息的操作封装了一下,登陆成功以后,会缓存token,或者用户信息以后,缓存。
使用useUserStroe
再login
页面,添加下面逻辑
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
userStore.login({
...ruleForm,
})
} else {
console.log('error submit!')
return false
}
})
}
每个我们定义的pinia,比如上面的useUserStore
,都有一个唯一的id:app-user
(不允许重复),在vue文件中使用的时候,可以通过const userStore = useUserStore()
,获取到对应store的所有信息,包活state、action、gettter
等。比之前的vuex简单多了。
集成router
官方文档:router.vuejs.org/
安装vue-router
pnpm add vue-router@4
创建路由实例
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
export const constantRoutes: Array<RouteRecordRaw> = [
{
path: '/',
component: () => import('@/views/home/index.vue'),
},
{
path: '/login',
component: () => import('@/views/login/index.vue'),
},
{
path: '/401',
component: () => import('@/views/error-page/index.vue'),
},
]
// 创建路由实例
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 }),
})
export default router
路由实例全局注册
// main.ts
import router from "@/router";
const app = createApp(App)
app.use(router)
app.mount('#app')
在页面访问/
、/login
、/401
路由的时候已经切换了。
此时,我们的项目目录为下面这样,
本文到此结束,下期预告:
- admin登陆流程
- Layout配置
- 路由权限控制,按钮权限控制
文章持续更新中。。。
代码地址
gitee.com/guigu-fe/gu… (欢迎star,欢迎PR)