Vue3搭建记录

news2024/9/20 22:38:34

一、初始化项目:项目名称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. 动态权限路由

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C#】async关键字修饰后有无await的影响

文章目录测试总结拓展&#xff1a;js的async await问题参考测试 来自微软官网的说法&#xff1a; 异步方法通常包含 await 运算符的一个或多个匹配项&#xff0c;但缺少 await 表达式不会导致编译器错误。 如果异步方法未使用 await 运算符标记悬挂点&#xff0c;则该方法将作…

RK3568工业开发板工控板说明

说明HW356X-GKA是采用中高端的通用型 SOC&#xff0c;一款基于Rockchip公司RK3568处理器的工控主板。主板标配处理器为Cortex-A55四核&#xff0c;最高主频2GHz的RK3568处理器&#xff0c;内置4GB DDR4内存(最大8GB)&#xff0c;32GB eMMC存储。集成4核 arm架构 A55 处理器和Ma…

记录使用ROS驱动USB相机

参考https://blog.csdn.net/weixin_48657767/article/details/126054121?spm1001.2014.3001.5502cmake编译时有bug&#xff0c;缺包&#xff0c;安装对应的包sudo apt-get install ros-melodic&#xff08;对应ros版本&#xff09;-****&#xff08;对应包名&#xff0c;下划线…

OpenGL学习日记之光照计算

引言 现实生活中的光照极其复杂&#xff0c;而且会收到很多因素的影响&#xff0c;是我们当前计算机的算力无法模拟的。因此我们会根据一些简化的模型来模拟现实光照&#xff0c;这样在可以模拟出近似的光照感受&#xff0c;但是又没有那么复杂的计算。 常用的光照模型有&…

27 pandas 数据透视

文章目录pivot_table 函数1、index需要聚合的列名&#xff0c;默认情况下聚合所有数据值的列2、values在结果透视的行上进行分组的列名或其它分组键【就是透视表里显示的列】3、columns在结果透视表的列上进行分组的列名或其它分组键4、Aggfunc聚合函数或函数列表&#xff08;默…

【1】linux命令每日分享——mkdir

大家好&#xff0c;这里是sdust-vrlab&#xff0c;Linux是一种免费使用和自由传播的类UNIX操作系统&#xff0c;Linux的基本思想有两点&#xff1a;一切都是文件&#xff1b;每个文件都有确定的用途&#xff1b;linux涉及到IT行业的方方面面&#xff0c;在我们日常的学习中&…

【Spring】难理解的Aop编程 | 入门?

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《spring开发》 坚持做好每一步&#xff0c;幸运之神自然会驾凌在你的身上 目录一. &#x1f981; 前言二. &#x1f981; 常见概念2.1 常见术语2.2 AOP入门Ⅰ. &#x1f407; 功能场景Ⅱ. &#x1f407; 实现过程2.3 通知类型Ⅰ.…

XXL-JOB分布式任务调度框架(三)-集群部署

文章目录 1.引言2.集群服务启动3.反向代理4.总结1.引言 XXL-JOB有中心化的思想,一旦调度中心挂机会导致整体不可使用,所以要引入集群。 需要考虑点: db配置保持一致登录账号配置保持一致集群机器时钟保持一致(单机集群可忽视)2.集群服务启动 在是在同一台机器中,并且在…

wav2vec 2.0:一种自监督的语音识别方法

总体框架&#xff1a; 主要分为2个大模块&#xff1a;1:语音特征提取模块 2:语音特征向量融合模块 1:特征提取模块 输入&#xff1a;音频 输出&#xff1a;音频特征向量 过程&#xff1a; 1&#xff09;跟具体采样率有关&#xff0c;如果一段1S的音频&#xff0c;采样率是1…

踔厉奋发·勇毅前行 | 2023广和通中国区代理商大会成功召开

2月16日&#xff0c;以“踔厉奋发 勇毅前行”为主题的2023广和通中国区代理商大会暨颁奖晚宴于深圳盛大开启并顺利落幕。广和通CEO应凌鹏、中国区销售部副总裁陈绮华等高层领导出席本次会议并发表演讲&#xff0c;与来自全国各地的代理商合作伙伴齐聚一堂&#xff0c;共话商机。…

云原生周刊 | 2023 年热门:云 IDE、Web Assembly 和 SBOM | 2023-02-20

在 CloudNative SecurityCon 上&#xff0c;云原生计算基金会的首席技术官 Chris Aniszczyk 在 The New Stack Makers 播客的这一集中强调了 2023 年正在形成几个趋势&#xff1a; 随着 GitHub 的 Codespaces 平台通过集成到 GitHub 服务中获得认可&#xff0c;云 IDE&#xf…

【SQL】MySQL秘籍

chihiro-notes 千寻简笔记 v0.1 内测版 &#x1f4d4; 笔记介绍 大家好&#xff0c;千寻简笔记是一套全部开源的企业开发问题记录&#xff0c;毫无保留给个人及企业免费使用&#xff0c;我是作者星辰&#xff0c;笔记内容整理并发布&#xff0c;内容有误请指出&#xff0c;笔…

用户行为分析项目MySQL+Tableau

文章目录1. 项目背景及目的1.1 项目背景1.2 项目目的2. 理解数据3. 数据预处理3.1 字段调整3.2 数据清洗3.2.1 空值3.2.2 重复值3.2.3 异常值4. 数据分析4.1 人4.1.1 获客情况&#xff08;PV、UV、PV/UV)4.1.2 留存情况&#xff08;留存率、跳失率&#xff09;4.1.3 行为情况&a…

数据在内存中的存储【上篇】

文章目录⚙️1.数据类型的详细介绍&#x1f529;1.1.类型的基本归类⚙️2.整型在内存中的存储&#x1f529;2.1.原码、反码、补码&#x1f529;2.2.大小端的介绍⚙️1.数据类型的详细介绍 &#x1f973;基本的内置类型 &#xff1a; &#x1f4a1;char ---------- 字符数据类型…

kubeadmin安装k8s集群

目录 一 、环境部署 1、服务器规划 2、环境准备 二、所有节点安装docker 1、配置yum源&#xff0c;安装docker 2、配置daemon.json文件 三、所有节点安装kubeadm、kubelet 和kubectl 四、部署k8s集群 1、查看初始化需要的镜像 2、导入镜像 3、初始化kubeadm 3.1 方…

【gt+】RS485详解

这里写目录标题RS232与RS485TTL和RS485电平转换平衡传输收发控制主机轮询手动带隔离的RS485电路自动切换电路RS485收发器发送器接收器网络安装电阻匹配接地问题网络失效保护RS232与RS485 RS232接口标准出现较早。 接口的电平值较高&#xff0c;易损坏接口电路的芯片&#xff…

Hive3 安装方式详解,datagrid自定义驱动连接hive

1 Hive的安装方式 hive的安装一共有三种方式:内嵌模式、本地模式、远程模式。 元数据服务(metastore&#xff09;作用是&#xff1a;客户端连接metastore服务&#xff0c;metastore再去连接MySQL数据库来存取元数据。有了metastore服务&#xff0c;就可以有多个客户端同时连接…

【部署】项目正式服部署更新

chihiro-notes 千寻简笔记 v0.1 内测版 &#x1f4d4; 笔记介绍 大家好&#xff0c;千寻简笔记是一套全部开源的企业开发问题记录&#xff0c;毫无保留给个人及企业免费使用&#xff0c;我是作者星辰&#xff0c;笔记内容整理并发布&#xff0c;内容有误请指出&#xff0c;笔…

SCRM的全面了解

一、什么是SCRM SCRM&#xff08;Social CRM&#xff0c;社会化客户关系管理&#xff09;&#xff0c;是以用户为中心&#xff0c;通过社交平台与用户建立联系&#xff0c;以内容、活动、客服、商城等服务吸引用户注意力&#xff0c;并不断与用户产生互动&#xff0c;实现用户…

离散数学笔记_第一章:逻辑和证明(1)

1.1命题逻辑1.1.1 命题 1.1.2 逻辑运算符 定义1&#xff1a; 否定联结词定义2&#xff1a; 合取联结词定义3&#xff1a; 析取联结词定义4&#xff1a; 异或联结词1.1.3 条件语句 定义5&#xff1a; 条件语句定义6&#xff1a; 双条件语句1.1.1 命题 1.命题&#xff1a;是…