一、初始化项目:项目名称vue3-element-admin
npm init vite@latest vue3-element-admin --template vue-ts
二、整合Element-Plus
1.本地安装Element Plus和图标组件
npm install element-plus
npm install @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')
3.Element Plus全局组件类型声明
// tsconfig.json
{"compilerOptions": {// ..."types": ["element-plus/global"]}
}
4.页面使用
<el-button type="primary">登录</el-button>
三、 路径别名配置:使用 @ 代替 src
1. 安装@types/node
npm install @types/node --save-dev
2. 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}}
})
3. TypeScript 编译配置
// tsconfig.json
{"compilerOptions": {"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录"paths": { //路径映射,相对于baseUrl"@/*": ["src/*"] },"allowSyntheticDefaultImports": true // 允许默认导入}
}
4.别名使用
四、多环境配置
1. 项目根目录:分别添加
开发环境:.env.development
生产环境:.env.production
测试环境:.env.test
# .env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE='vue3-element-admin'
VITE_APP_PORT=3000
VITE_APP_BASE_API='/dev-api'
# .env.production
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE='vue3-element-admin'
VITE_APP_PORT=3000
VITE_APP_BASE_API='/prod-api'
# .env.test
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE='vue3-element-admin'
VITE_APP_PORT=3000
VITE_APP_BASE_API='/test-api'
2.WebStorm插件
3.环境变量智能提示
在src下新建文件env.d.ts,内容如下:
// src/ env.d.ts
// 环境变量类型声明
interface ImportMetaEnv {VITE_APP_TITLE: string,VITE_APP_PORT: string,VITE_APP_BASE_API: string
}interface ImportMeta {readonly env: ImportMetaEnv
}
五、Vite 配置反向代理解决跨域
修改vite.config.ts文件为如下:
// vite.config.ts
import {UserConfig, ConfigEnv, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'export default ({command, mode}: ConfigEnv): UserConfig => {// 获取 .env 环境配置文件const env = loadEnv(mode, process.cwd())return ({plugins: [vue()],// 本地反向代理解决浏览器跨域限制server: {host: 'localhost',port: Number(env.VITE_APP_PORT),open: true, // 启动是否自动打开浏览器proxy: {[env.VITE_APP_BASE_API]: {target: 'http://vapi.youlai.tech', // 有来商城线上接口地址changeOrigin: true,rewrite: path => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')}}},resolve: {alias: {"@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src}}})
}
F12我们看到访问的是本地,实际上内部已经将http://localhost:3000/dev-api/换成了http://vapi.youlai.tech/
至此基本环境搭建已经搭建成功,接下来是三大件的集成:Pinia、Axios、Vue-router。
三大件是项目交叉依赖的,Pinia需要存储用户信息,用户信息需要Axios来获取,Vue-router需要Axios来获取角色等等。所以只有三大件完全组件完毕才能启动成功!
npm install better-scroll -S
npm install echarts --save
npm install sass
npm install -D path-browserify
npm install -D path-to-regexp
npm install @wangeditor/editor-for-vue@next
npm install vue-i18n@next
六、Pinia状态管理
1. 安装Pinia
npm install pinia
2. Pinia全局注册
在src下新建store文件夹,然后在store文件夹下新建index.ts内容如下:
// src/store/index.ts
import type { App } from 'vue';
import { createPinia } from 'pinia';const store = createPinia();// 全局挂载store
export function setupStore(app: App<Element>) {app.use(store);
}export { store };
修改main.ts为如下:
// src/main.ts
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/index.css'import {setupStore} from '@/store';const app = createApp(App);
// 全局挂载
setupStore(app);app.use(ElementPlus).mount('#app');
3.Pinia模块封装
在store文件夹下新建modules文件夹,这里以用户状态为例:在modules文件夹下新建user文件夹,在于user文件夹下新建index.ts和types.ts内容如下:
// src/store/modules/user/index.ts
import { defineStore } from 'pinia';import { store } from '@/store';
import { ref } from 'vue';export const useUserStore = defineStore('user', () => {// stateconst token = ref<string>('');const nickname = ref<string>('');const avatar = ref<string>('');const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限const perms = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限// actions// 登录function login(loginData: any) {return new Promise<void>((resolve, reject) => {console.log(loginData)// loginApi(loginData) // 调用登录API});}// 获取信息(用户昵称、头像、角色集合、权限集合)function getInfo() {return new Promise<any>((resolve, reject) => {// getUserInfo() // 调用获取用户信息API});}// 注销function logout() {return new Promise<void>((resolve, reject) => {// logoutApi() // 调用注销API});}// 重置function resetToken() {// removeToken(); 调用删除Token方法token.value = '';nickname.value = '';avatar.value = '';roles.value = [];perms.value = [];}return {token,nickname,avatar,roles,perms,login,getInfo,logout,resetToken};
});// 非setup
export function useUserStoreHook() {return useUserStore(store);
}
4.使用Pinia
①setup调用
②非setup调用
七、Axios网络请求库封装
1. 安装Axios和js-cookie
npm install --save js-cookie
npm install --save @types/js-cookie
npm install axios
2.axios工具封装
在src下新建utils文件夹,然后在utils文件下新建auth.ts、localStorage.ts、request.ts,内容如下:
// src/utils/auth.ts
import Cookies from 'js-cookie';const TokenKey = 'vue3-element-admin-token';export function getToken() {return Cookies.get(TokenKey);
}export function setToken(token: string) {Cookies.set(TokenKey, token);
}export function removeToken() {return Cookies.remove(TokenKey);
}
// src/utils/request.ts
import axios, { AxiosRequestConfig,InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getToken } from '@/utils/auth';
import { useUserStoreHook } from '@/store/modules/user';// 创建 axios 实例
const service = axios.create({baseURL: import.meta.env.VITE_APP_BASE_API,timeout: 50000,headers: { 'Content-Type': 'application/json;charset=utf-8' }
});// 请求拦截器
service.interceptors.request.use((config: InternalAxiosRequestConfig<any>) => {if (!config.headers) {throw new Error(`Expected 'config' and 'config.headers' not to be undefined`);}const user = useUserStoreHook();if (user.token) {(config.headers as any).Authorization = getToken();}return config;},(error: any) => {return Promise.reject(error);}
);// 响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {const { code, msg } = response.data;if (code === '00000') {return response.data;} else {// 响应数据为二进制流处理(Excel导出)if (response.data instanceof ArrayBuffer) {return response;}ElMessage({message: msg || '系统出错',type: 'error'});return Promise.reject(new Error(msg || 'Error'));}},(error: any) => {if (error.response.data) {const { code, msg } = error.response.data;// token 过期,重新登录if (code === 'A0230') {ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', {confirmButtonText: 'OK',type: 'warning'}).then(() => {localStorage.clear();window.location.href = '/';});} else {ElMessage({message: msg || '系统出错',type: 'error'});}}return Promise.reject(error.message);}
);// 导出 axios 实例
export default service;
3.API封装
以登录、并获取用户信息(昵称、头像、角色集合和权限集合)的接口为案例,演示如何通过封装的 axios 工具类请求后端接口,获取响应数据。
①在src下新建types文件夹,然后在types文件夹下新建global.d.ts内容如下:
declare global {interface PageQuery {pageNum: number;pageSize: number;}interface PageResult<T> {list: T;total: number;}type DialogType = {title?: string;visible: boolean;};type OptionType = {value: string;label: string;checked?: boolean;children?: OptionType[];};
}
export {};
②在src下新建api文件夹,然后在api下新建auth文件夹,然后在auth文件夹下新建index.ts和types.ts,内容如下:
// src/api/auth/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { LoginData, TokenResult, VerifyCode } from './types';/**** @param data {LoginForm}* @returns*/
export function loginApi(data: LoginData): AxiosPromise<TokenResult> {return request({url: '/api/v1/auth/login',method: 'post',params: data});
}/*** 注销*/
export function logoutApi() {return request({url: '/api/v1/auth/logout',method: 'delete'});
}/*** 获取图片验证码*/
export function getCaptcha(): AxiosPromise<VerifyCode> {return request({url: '/captcha?t=' + new Date().getTime().toString(),method: 'get'});
}
// src/api/auth/types.ts
/*** 登录数据类型*/
export interface LoginData {username: string;password: string;/*** 验证码Code*///verifyCode: string;/*** 验证码Code服务端缓存key(UUID)*/// verifyCodeKey: string;
}/*** Token响应类型*/
export interface TokenResult {accessToken: string;refreshToken: string;expires: number;
}/*** 验证码类型*/
export interface VerifyCode {verifyCodeImg: string;verifyCodeKey: string;
}
③在api下新建user文件夹,然后在user文件夹下新建index.ts和types.ts,内容如下:
// src/api/user/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { UserForm, UserInfo, UserPageResult, UserQuery } from './types';/*** 登录成功后获取用户信息(昵称、头像、权限集合和角色集合)*/
export function getUserInfo(): AxiosPromise<UserInfo> {return request({url: '/api/v1/users/me',method: 'get'});
}/*** 获取用户分页列表** @param queryParams*/
export function listUserPages(queryParams: UserQuery
): AxiosPromise<UserPageResult> {return request({url: '/api/v1/users/pages',method: 'get',params: queryParams});
}/*** 获取用户表单详情** @param userId*/
export function getUserForm(userId: number): AxiosPromise<UserForm> {return request({url: '/api/v1/users/' + userId + '/form',method: 'get'});
}/*** 添加用户** @param data*/
export function addUser(data: any) {return request({url: '/api/v1/users',method: 'post',data: data});
}/*** 修改用户** @param id* @param data*/
export function updateUser(id: number, data: UserForm) {return request({url: '/api/v1/users/' + id,method: 'put',data: data});
}/*** 修改用户状态** @param id* @param status*/
export function updateUserStatus(id: number, status: number) {return request({url: '/api/v1/users/' + id + '/status',method: 'patch',params: { status: status }});
}/*** 修改用户密码** @param id* @param password*/
export function updateUserPassword(id: number, password: string) {return request({url: '/api/v1/users/' + id + '/password',method: 'patch',params: { password: password }});
}/*** 删除用户** @param ids*/
export function deleteUsers(ids: string) {return request({url: '/api/v1/users/' + ids,method: 'delete'});
}/*** 下载用户导入模板** @returns*/
export function downloadTemplate() {return request({url: '/api/v1/users/template',method: 'get',responseType: 'arraybuffer'});
}/*** 导出用户** @param queryParams* @returns*/
export function exportUser(queryParams: UserQuery) {return request({url: '/api/v1/users/_export',method: 'get',params: queryParams,responseType: 'arraybuffer'});
}/*** 导入用户** @param file*/
export function importUser(deptId: number, roleIds: string, file: File) {const formData = new FormData();formData.append('file', file);formData.append('deptId', deptId.toString());formData.append('roleIds', roleIds);return request({url: '/api/v1/users/_import',method: 'post',data: formData,headers: {'Content-Type': 'multipart/form-data'}});
}
// src/api/user/types.ts
/*** 登录用户信息*/
export interface UserInfo {nickname: string;avatar: string;roles: string[];perms: string[];
}/*** 用户查询参数*/
export interface UserQuery extends PageQuery {keywords: string;status: number;deptId: number;
}/*** 用户分页列表项声明*/
export interface UserType {id: string;username: string;nickname: string;mobile: string;gender: number;avatar: string;email: string;status: number;deptName: string;roleNames: string;createTime: string;
}/*** 用户分页项类型声明*/
export type UserPageResult = PageResult<UserType[]>;/*** 用户表单类型声明*/
export interface UserForm {id: number | undefined;deptId: number;username: string;nickname: string;password: string;mobile: string;email: string;gender: number;status: number;remark: string;roleIds: number[];
}/*** 用户导入表单类型声明*/
export interface UserImportData {deptId: number;roleIds: number[];
}
4. API调用
八、路由vue-router
1.安装 vue-router
npm install vue-router@next
2. 创建路由实例
创建路由实例并导出,其中包括静态路由数据,动态路由后面将通过接口从后端获取并整合用户角色的权限控制。
①.在src下新建router文件夹,在router文件夹下新建index.ts内容如下:
②.在src下新建views文件夹,并在views下新建文件夹dashboard、login、redirect,以及相应的index.vue文件,在views下新建文件夹error-page并在其中新建404.vue、401.vue.
③在api文件夹下新建menu文件夹,并在其中新建index.ts和types.ts
④在根目录下新建types文件夹,并在其中新建global.d.ts内容如下:
③修改store/index.ts为如下
④在src文件夹下新建global.d.ts内容如下:
③.在store/modules文件夹下新建user和permission文件夹,并分别在其中新建index.ts内容如下
3. 路由实例全局注册
// main.ts
import router from "@/router";app.use(router).mount('#app')
4. 动态权限路由