一、初始化项目:项目名称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', () => {
// state
const 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/localStorage.ts
/**
* window.localStorage 浏览器永久缓存
*/
export const localStorage = {
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
const json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
}
};
// 侧边栏状态(显示/隐藏)
const SidebarStatusKey = 'sidebarStatus';
export function getSidebarStatus() {
return localStorage.get(SidebarStatusKey);
}
export function setSidebarStatus(sidebarStatus: string) {
localStorage.set(SidebarStatusKey, sidebarStatus);
}
// 布局大小
const SizeKey = 'size';
export function getSize() {
return localStorage.get(SizeKey);
}
export function setSize(size: string) {
localStorage.set(SizeKey, size);
}
// 语言
const LanguageKey = 'language';
export function getLanguage() {
return localStorage.get(LanguageKey);
}
export function setLanguage(language: string) {
localStorage.set(LanguageKey, language);
}
3.API封装
以登录成功后获取用户信息(昵称、头像、角色集合和权限集合)的接口为案例,演示如何通过封装的 axios 工具类请求后端接口,获取响应数据。
在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[];
}
在api下新建role文件夹,然后在role文件夹下新建index.ts和types.ts,内容如下:
// src/api/role/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { RoleQuery, RolePageResult, RoleForm } from './types';
/**
* 获取角色分页数据
*
* @param queryParams
*/
export function listRolePages(
queryParams?: RoleQuery
): AxiosPromise<RolePageResult> {
return request({
url: '/api/v1/roles/pages',
method: 'get',
params: queryParams
});
}
/**
* 获取角色下拉数据
*
* @param queryParams
*/
export function listRoleOptions(
queryParams?: RoleQuery
): AxiosPromise<OptionType[]> {
return request({
url: '/api/v1/roles/options',
method: 'get',
params: queryParams
});
}
/**
* 获取角色拥有的资源ID集合
*
* @param queryParams
*/
export function getRoleMenuIds(roleId: string): AxiosPromise<number[]> {
return request({
url: '/api/v1/roles/' + roleId + '/menuIds',
method: 'get'
});
}
/**
* 修改角色资源权限
*
* @param queryParams
*/
export function updateRoleMenus(
roleId: string,
data: number[]
): AxiosPromise<any> {
return request({
url: '/api/v1/roles/' + roleId + '/menus',
method: 'put',
data: data
});
}
/**
* 获取角色详情
*
* @param id
*/
export function getRoleDetail(id: number): AxiosPromise<RoleForm> {
return request({
url: '/api/v1/roles/' + id,
method: 'get'
});
}
/**
* 添加角色
*
* @param data
*/
export function addRole(data: RoleForm) {
return request({
url: '/api/v1/roles',
method: 'post',
data: data
});
}
/**
* 更新角色
*
* @param id
* @param data
*/
export function updateRole(id: number, data: RoleForm) {
return request({
url: '/api/v1/roles/' + id,
method: 'put',
data: data
});
}
/**
* 批量删除角色,多个以英文逗号(,)分割
*
* @param ids
*/
export function deleteRoles(ids: string) {
return request({
url: '/api/v1/roles/' + ids,
method: 'delete'
});
}
// src/api/role/types.ts
/**
* 角色查询参数类型
*/
export interface RoleQuery extends PageQuery {
keywords?: string;
}
/**
* 角色分页列表项
*/
export interface Role {
id: string;
name: string;
code: string;
sort: number;
status: number;
deleted: number;
menuIds?: any;
permissionIds?: any;
}
/**
* 角色分页项类型
*/
export type RolePageResult = PageResult<Role[]>;
/**
* 角色表单
*/
export interface RoleForm {
id?: number;
name: string;
code: string;
sort: number;
status: number;
/**
* 数据权限
*/
dataScope: number;
}
在api下新建menu文件夹,然后在menu文件夹下新建index.ts和types.ts,内容如下:
// src/api/menu/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MenuQuery, Menu, Resource, MenuForm } from './types';
/**
* 获取路由列表
*/
export function listRoutes() {
return request({
url: '/api/v1/menus/routes',
method: 'get'
});
}
/**
* 获取菜单表格列表
*
* @param queryParams
*/
export function listMenus(queryParams: MenuQuery): AxiosPromise<Menu[]> {
return request({
url: '/api/v1/menus',
method: 'get',
params: queryParams
});
}
/**
* 获取菜单下拉树形列表
*/
export function listMenuOptions(): AxiosPromise<OptionType[]> {
return request({
url: '/api/v1/menus/options',
method: 'get'
});
}
/**
* 获取资源(菜单+权限)树形列表
*/
export function listResources(): AxiosPromise<Resource[]> {
return request({
url: '/api/v1/menus/resources',
method: 'get'
});
}
/**
* 获取菜单详情
* @param id
*/
export function getMenuDetail(id: string): AxiosPromise<MenuForm> {
return request({
url: '/api/v1/menus/' + id,
method: 'get'
});
}
/**
* 添加菜单
*
* @param data
*/
export function addMenu(data: MenuForm) {
return request({
url: '/api/v1/menus',
method: 'post',
data: data
});
}
/**
* 修改菜单
*
* @param id
* @param data
*/
export function updateMenu(id: string, data: MenuForm) {
return request({
url: '/api/v1/menus/' + id,
method: 'put',
data: data
});
}
/**
* 批量删除菜单
*
* @param ids 菜单ID,多个以英文逗号(,)分割
*/
export function deleteMenus(ids: string) {
return request({
url: '/api/v1/menus/' + ids,
method: 'delete'
});
}
// src/api/menu/types.ts
/**
* 菜单查询参数类型声明
*/
export interface MenuQuery {
keywords?: string;
}
/**
* 菜单分页列表项声明
*/
export interface Menu {
id?: number;
parentId: number;
type?: string | 'CATEGORY' | 'MENU' | 'EXTLINK';
createTime: string;
updateTime: string;
name: string;
icon: string;
component: string;
sort: number;
visible: number;
children: Menu[];
}
/**
* 菜单表单类型声明
*/
export interface MenuForm {
/**
* 菜单ID
*/
id?: string;
/**
* 父菜单ID
*/
parentId: string;
/**
* 菜单名称
*/
name: string;
/**
* 菜单是否可见(1:是;0:否;)
*/
visible: number;
icon?: string;
/**
* 排序
*/
sort: number;
/**
* 组件路径
*/
component?: string;
/**
* 路由路径
*/
path: string;
/**
* 跳转路由路径
*/
redirect?: string;
/**
* 菜单类型
*/
type: string;
/**
* 权限标识
*/
perm?: string;
}
/**
* 资源(菜单+权限)类型
*/
export interface Resource {
/**
* 菜单值
*/
value: string;
/**
* 菜单文本
*/
label: string;
/**
* 子菜单
*/
children: Resource[];
}
/**
* 权限类型
*/
export interface Permission {
/**
* 权限值
*/
value: string;
/**
* 权限文本
*/
label: string;
}
4. API调用
八、路由vue-router
1.安装 vue-router
npm install vue-router@next
2. 创建路由实例
创建路由实例并导出,其中包括静态路由数据,动态路由后面将通过接口从后端获取并整合用户角色的权限控制。
①.在src下新建router文件夹,在router文件夹下新建index.ts内容如下:
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { usePermissionStoreHook } from '@/store/modules/permission';
export const Layout = () => import('@/layout/index.vue');
// 静态路由
export const constantRoutes: RouteRecordRaw[] = [
{
path: '/redirect',
component: Layout,
meta: { hidden: true },
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/404',
component: () => import('@/views/error-page/404.vue'),
meta: { hidden: true }
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'homepage', affix: true }
},
{
path: '401',
component: () => import('@/views/error-page/401.vue'),
meta: { hidden: true }
}
]
}
];
// 创建路由
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 })
});
// 重置路由
export function resetRouter() {
const permissionStore = usePermissionStoreHook();
permissionStore.routes.forEach(route => {
const name = route.name;
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
}
export default router;
②.在src下新建views文件夹,并在views下新建文件夹dashboard、login、redirect,以及相应的index.vue文件,在views下新建文件夹error-page并在其中新建404.vue、401.vue.
③在api文件夹下新建menu文件夹,并在其中新建index.ts和types.ts
// src/api/menu/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MenuQuery, Menu, Resource, MenuForm } from './types';
/**
* 获取路由列表
*/
export function listRoutes() {
return request({
url: '/api/v1/menus/routes',
method: 'get'
});
}
/**
* 获取菜单表格列表
*
* @param queryParams
*/
export function listMenus(queryParams: MenuQuery): AxiosPromise<Menu[]> {
return request({
url: '/api/v1/menus',
method: 'get',
params: queryParams
});
}
/**
* 获取菜单下拉树形列表
*/
export function listMenuOptions(): AxiosPromise<OptionType[]> {
return request({
url: '/api/v1/menus/options',
method: 'get'
});
}
/**
* 获取资源(菜单+权限)树形列表
*/
export function listResources(): AxiosPromise<Resource[]> {
return request({
url: '/api/v1/menus/resources',
method: 'get'
});
}
/**
* 获取菜单详情
* @param id
*/
export function getMenuDetail(id: string): AxiosPromise<MenuForm> {
return request({
url: '/api/v1/menus/' + id,
method: 'get'
});
}
/**
* 添加菜单
*
* @param data
*/
export function addMenu(data: MenuForm) {
return request({
url: '/api/v1/menus',
method: 'post',
data: data
});
}
/**
* 修改菜单
*
* @param id
* @param data
*/
export function updateMenu(id: string, data: MenuForm) {
return request({
url: '/api/v1/menus/' + id,
method: 'put',
data: data
});
}
/**
* 批量删除菜单
*
* @param ids 菜单ID,多个以英文逗号(,)分割
*/
export function deleteMenus(ids: string) {
return request({
url: '/api/v1/menus/' + ids,
method: 'delete'
});
}
// src/api/menu/types.ts
/**
* 菜单查询参数类型声明
*/
export interface MenuQuery {
keywords?: string;
}
/**
* 菜单分页列表项声明
*/
export interface Menu {
id?: number;
parentId: number;
type?: string | 'CATEGORY' | 'MENU' | 'EXTLINK';
createTime: string;
updateTime: string;
name: string;
icon: string;
component: string;
sort: number;
visible: number;
children: Menu[];
}
/**
* 菜单表单类型声明
*/
export interface MenuForm {
/**
* 菜单ID
*/
id?: string;
/**
* 父菜单ID
*/
parentId: string;
/**
* 菜单名称
*/
name: string;
/**
* 菜单是否可见(1:是;0:否;)
*/
visible: number;
icon?: string;
/**
* 排序
*/
sort: number;
/**
* 组件路径
*/
component?: string;
/**
* 路由路径
*/
path: string;
/**
* 跳转路由路径
*/
redirect?: string;
/**
* 菜单类型
*/
type: string;
/**
* 权限标识
*/
perm?: string;
}
/**
* 资源(菜单+权限)类型
*/
export interface Resource {
/**
* 菜单值
*/
value: string;
/**
* 菜单文本
*/
label: string;
/**
* 子菜单
*/
children: Resource[];
}
/**
* 权限类型
*/
export interface Permission {
/**
* 权限值
*/
value: string;
/**
* 权限文本
*/
label: string;
}
④在根目录下新建types文件夹,并在其中新建global.d.ts内容如下:
// 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/utils/request.ts
import axios, { AxiosRequestConfig, 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) => {
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;
③修改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 };
④在src文件夹下新建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 {};
③.在store/modules文件夹下新建user和permission文件夹,并分别在其中新建index.ts内容如下
3. 路由实例全局注册
// main.ts
import router from "@/router";
app.use(router)
.mount('#app')
4. 动态权限路由