# vue3-admin-test **Repository Path**: jseven68/vue3-admin-test ## Basic Information - **Project Name**: vue3-admin-test - **Description**: 新的vue3练习项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-05-17 - **Last Updated**: 2024-10-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: vue3, vite, Element-UI ## README # Vue 3 + TypeScript + Vite ## 搭建 1. 初始化项目 按照 🍃Vite 官方文档 - 搭建第一个 Vite 项目 说明,执行以下命令完成 vue 、typescirpt 模板项目的初始化 > pnpm create vite my-vue-app --template vue-ts > my-vue-app 项目名称 > vue-ts: vue + typescript 模板的标识,查看 create-vite 以获取每个模板的更多细节:vue,vue-ts,react,react-ts 2. 安装包 ```js pnpm install pnpm run dev ``` ## 配置别名 1. 配置 > 需要修改 vite.config.ts 文件,添加别名配置 ```js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@views': path.resolve(__dirname, './src/views'), '@utils': path.resolve(__dirname, './src/utils'), '@store': path.resolve(__dirname, './src/store'), '@router': path.resolve(__dirname, './src/router'), '@assets': path.resolve(__dirname, './src/assets'), '@api': path.resolve(__dirname, './src/api'), }, }, }) ``` 2. 安装@types/node >import path from 'path'编译器报错:TS2307: Cannot find module 'path' or its corresponding type declarations. 本地安装 Node 的 TypeScript 类型描述文件即可解决编译器报错 ```js pnpm install @types/node --save-dev ``` ` 3. TypeScript 编译配置 同样还是import path from 'path' 编译报错: TS1259: Module '"path"' can only be default-imported using the 'allowSyntheticDefaultImports' flag 因为 typescript 特殊的 import 方式 , 需要配置允许默认导入的方式,还有路径别名的配置 ```json // tsconfig.json { "compilerOptions": { "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录 "paths": { //路径映射,相对于baseUrl "@/*": ["src/*"] }, "allowSyntheticDefaultImports": true // 允许默认导入 } } ``` ## 自动导入 unplugin 自动导入 Element Plus 官方文档中推荐 按需自动导入 的方式,而此需要使用额外的插件 unplugin-auto-import 和 unplugin-vue-components 来导入要使用的组件。所以在整合 Element Plus 之前先了解下自动导入的概念和作用 概念 为了避免在多个页面重复引入 API 或 组件,由此而产生的自动导入插件来节省重复代码和提高开发效率。 插件概念自动导入对象unplugin-auto-import按需自动导入APIref,reactive,watch,computed 等APIunplugin-vue-components按需自动导入组件Element Plus 等三方库和指定目录下的自定义组件. ### 安装 ```js pnpm install unplugin-auto-import unplugin-vue-components -D ``` ### 配置 配置完保存,会发现src/types/目录下自动生成了导入文件了, 然后组件使用vue的api和导入src/componets下组件都会自动导入,不需要手动导入了 ```ts // vite.config.ts import AutoImport from "unplugin-auto-import/vite"; import Components from "unplugin-vue-components/vite"; import path from "path"; const pathSrc = path.resolve(__dirname, "src"); plugins: [ AutoImport({ // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 imports: ["vue"], eslintrc: { enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false ,配置true会目录下生成eslint规则文件,看到生成了就可以设置false了 filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件 }, dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径,配置了文件会自动生成,且引用新组件会自动导入 }), Components({ dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径 }), ] ``` ### .eslintrc.cjs - 自动导入函数 eslint 规则引入 ```ts "extends": [ "./.eslintrc-auto-import.json" ], ``` ### tsconfig.json 自动导入TS类型声明文件引入 ```json { "include": ["src/**/*.d.ts"] } ``` ### ElementPlus引入 需要完成上面一节的 自动导入 的安装和配置 #### 安装 ```js npm install element-plus pnpm i -D unplugin-icons // 安装自动导入 Icon 依赖 ``` > vite.cconfig.ts 配置,参考官网 ```ts // vite.config.ts import vue from "@vitejs/plugin-vue"; import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite"; import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; import Icons from "unplugin-icons/vite"; import IconsResolver from "unplugin-icons/resolver"; export default ({ mode }: ConfigEnv): UserConfig => { return { plugins: [ // ... AutoImport({ // ... resolvers: [ // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式) ElementPlusResolver(), // 自动导入图标组件 IconsResolver({}), ] vueTemplate: true, // 是否在 vue 模板中自动导入 dts: path.resolve(pathSrc, 'types', 'auto-imports.d.ts') // 自动导入组件类型声明文件位置,默认根目录 }), Components({ resolvers: [ // 自动导入 Element Plus 组件 ElementPlusResolver(), // 自动注册图标组件 IconsResolver({ enabledCollections: ["ep"] // element-plus图标库,其他图标库 https://icon-sets.iconify.design/ }), ], dts: path.resolve(pathSrc, "types", "components.d.ts"), // 自动导入组件类型声明文件位置,默认根目录 }), Icons({ // 自动安装图标库 autoInstall: true, }), ], }; }; ``` #### 使用 ```vue
Success Info
``` #### 整合svg > 通过 vite-plugin-svg-icons 插件整合 Iconfont 第三方图标库实现本地图标 1. 安装 ```js pnpm install -D fast-glob@3.2.11 pnpm install -D vite-plugin-svg-icons@2.0.1 ``` 2. 创建目录 创建 src/assets/icons 目录 , 放入从 Iconfont 复制的 svg 图标 3. main.ts 引入注册脚本 ```js // src/main.ts import 'virtual:svg-icons-register'; ``` 4. vite.config.ts 配置插件 ```ts // vite.config.ts import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; export default ({command, mode}: ConfigEnv): UserConfig => { return ( { plugins: [ createSvgIconsPlugin({ // 指定需要缓存的图标文件夹 iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], // 指定symbolId格式 symbolId: 'icon-[dir]-[name]', }) ] } ) } ``` 5. 创建组件 参考项目 src/components/SvgIcon.vue 6. 使用 icon-class="xxx" ```vue SVG 本地图标 ``` ## 整合scss > 安装 ```js pnpm install -D sass ``` > vite.config.ts 配置 全局引入 ```ts // vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [ vue(), ], css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/variables.scss";`, }, }, } ``` > 配置ts文件使用的scss变量 创建 variables.module.scss 文件,vue3中xxx.module.css就是一个css模块文件 ```scss // 导出 variables.scss 文件的变量 :export{ bgColor: $--primary-color // variables.scss 文件中的变量 } ``` 使用 ```ts ``` ## 整合 UnoCSS > 安装 ```js pnpm install -D unocss ``` > vite.config.ts 配置 ```ts // vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import Unocss from 'unocss/vite'; export default defineConfig({ plugins: [ vue(), UnoCSS(), ], }) ``` > main.ts 配置 ```ts // src/main.ts // src/main.ts import 'uno.css' ``` VSCode 安装 UnoCSS 插件 ## 整合Pinia > 安装 ```js pnpm install pinia ``` > main.ts 配置 ```ts // src/main.ts import { createApp } from 'vue'; import App from './App.vue'; import { createPinia } from 'pinia'; const app = createApp(App); const pinia = createPinia(); app.use(pinia); app.mount('#app'); ``` > src/store/index.ts 配置 ```ts // src/store/index.ts import { defineStore } from 'pinia'; export const useMainStore = defineStore('main', { state: () => ({ count: 0, }), actions: { increment() { this.count++; }, }, }); // 上面是选项式写法,也支持组合式 import { defineStore } from "pinia"; // 组合式 export const useCounterStore = defineStore('counter',()=>{ const count = ref(0) // 定义其他状态和方法 const increment = () => { count.value++ } const double = computed(() => count.value * 2) return { count, increment, double } }) ``` > src/views/Home.vue 使用 ```vue ``` ## 整合环境变量 > Vite 环境变量主要是为了区分开发、测试、生产等环境的变量,详细查看vite文档 ### env配置文件 项目根目录新建 .env.development 、.env.production 开发环境变量配置:.env.development ```ts properties复制代码# 变量必须以 VITE_ 为前缀才能暴露给外部读取 VITE_APP_TITLE = 'vue3-element-admin' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/dev-api' ``` 生产环境变量配置:.env.production ```ts properties复制代码VITE_APP_TITLE = 'vue3-element-admin' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/prod-api' ``` ### 环境变量智能提示 新建 src/types/env.d.ts文件存放环境变量TS类型声明 ```ts // src/types/env.d.ts interface ImportMetaEnv { /** * 应用标题 */ VITE_APP_TITLE: string; /** * 应用端口 */ VITE_APP_PORT: number; /** * API基础路径(反向代理) */ VITE_APP_BASE_API: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` ### 打包配置 ```json { "name": "vue3-admin-test", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite serve --mode development", "build:prod": "vite build --mode production && vue-tsc --noEmit", "preview": "vite preview" }, } ``` ## 配置本地代理 ```ts // vite.config.ts import { defineConfig } from 'vite'; export default defineConfig({ server: { // 允许IP访问 host: "0.0.0.0", // 应用端口 (默认:3000) port: Number(env.VITE_APP_PORT), // 运行是否自动打开浏览器 open: true, proxy: { /** 代理前缀为 /dev-api 的请求 */ [env.VITE_APP_BASE_API]: { changeOrigin: true, // 接口地址 target: env.VITE_APP_API_URL, rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), }, }, }, }) ``` ## 整合axios > 安装 ```js pnpm install axios ``` > src/utils/request.ts 配置 ```ts // src/utils/request.ts /** axios封装 */ import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { useUserStore } 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) => { const userStore = useUserStore(); if (userStore.token) { config.headers.Authorization = userStore.token; } 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; } ElMessage.error(msg || '系统出错'); 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: '确定', type: 'warning' }).then(() => { localStorage.clear(); // @vueuse/core 自动导入 window.location.href = '/'; }); }else{ ElMessage.error(msg || '系统出错'); } } return Promise.reject(error.message); } ); // 导出 axios 实例 export default service; ``` ## Api 定义 登录接口举例: ### 类型定义 将类型定义复制到 src/api/auth/types.ts 文件中 ```ts /** * 登录请求参数 */ export interface LoginData { /** * 用户名 */ username: string; /** * 密码 */ password: string; } /** * 登录响应 */ export interface LoginResult { /** * 访问token */ accessToken?: string; /** * 过期时间(单位:毫秒) */ expires?: number; /** * 刷新token */ refreshToken?: string; /** * token 类型 */ tokenType?: string; } ``` ### 接口定义 ```ts // src/api/auth/index.ts import request from '@/utils/request'; import { AxiosPromise } from 'axios'; import { LoginData, LoginResult } from './types'; /** * 登录API * * @param data {LoginData} * @returns */ export function loginApi(data: LoginData): AxiosPromise { return request({ url: '/api/v1/auth/login', method: 'post', params: data }); } ``` ### 接口调用 ```ts // src/store/modules/user.ts import { loginApi } from '@/api/auth'; import { LoginData } from '@/api/auth/types'; /** * 登录调用 * * @param {LoginData} * @returns */ function login(loginData: LoginData) { return new Promise((resolve, reject) => { loginApi(loginData) .then(response => { const { tokenType, accessToken } = response.data; token.value = tokenType + ' ' + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx resolve(); }) .catch(error => { reject(error); }); }); } ``` ## 整合路由 1. 安装 > npm install vue-router@next ### 路由守卫 ```ts // src/router/index.ts import { createRouter, createWebHistory } from 'vue-router'; import { routes } from './routes'; import { useUserStore } from '@/store/modules/user'; const router = createRouter({ history: createWebHistory(), routes }); router.beforeEach(async (to, from, next) => { const userStore = useUserStore(); if (to.path === '/login') { // 如果是登录页面,直接放行 next(); } // 获取token const token = userStore.token; // 如果token存在,直接放行 if (token) { next(); } else { // 如果token不存在,跳转到登录页面 next('/login'); } }); export default router; ``` ### 全局注册 ```ts // main.ts import router from "@/router"; app.use(router).mount('#app') ``` ## 按钮权限 通过自定义指令方式 ```ts // src/directive/permission/index.ts import { useUserStoreHook } from '@/store/modules/user'; import { Directive, DirectiveBinding } from 'vue'; /** * 按钮权限 */ export const hasPerm: Directive = { mounted(el: HTMLElement, binding: DirectiveBinding) { // 「超级管理员」拥有所有的按钮权限 const { roles, perms } = useUserStoreHook().user; if (roles.includes('ROOT')) { return true; } // 「其他角色」按钮权限校验 const { value } = binding; if (value) { const requiredPerms = value; // DOM绑定需要的按钮权限标识 const hasPerm = perms?.some(perm => { return requiredPerms.includes(perm); }); if (!hasPerm) { el.parentNode && el.parentNode.removeChild(el); } } else { throw new Error( "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\"" ); } } }; ``` ### 全局注册 ```ts // src/directive/index.ts import type { App } from 'vue'; import { hasPerm } from './permission'; // 全局注册 directive 方法 export function setupDirective(app: App) { // 使 v-hasPerm 在所有组件中都可用 app.directive('hasPerm', hasPerm); } ``` ```ts // src/main.ts import { setupDirective } from '@/directive'; const app = createApp(App); // 全局注册 自定义指令(directive) setupDirective(app); ``` ### 使用 ```html 新增 修改 删除 ``` `