使用vite构建vue3项目详细介绍(ts+pinia+sass+vue-router+axios+element-plus)
npm init vite@latest

vite.config.tspath需要安装--npm install @types/node --save-dev
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import { defineConfig, loadEnv, ConfigEnv } from 'vite';const alias: Record = {'@': resolve(__dirname, '.', './src') // 设置文件./src路径为 @
};const viteConfig = defineConfig((mode: ConfigEnv) => {const env = loadEnv(mode.mode, process.cwd());return {plugins: [vue()],resolve: { alias },base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH, // 打包路径server: {host: '0.0.0.0',port: env.VITE_PORT as unknown as number, // 服务端口号open: true, // 服务启动时是否自动打开浏览器hmr:true, // 开启热更新},};
});export default viteConfig;
ts.config.json{"compilerOptions": {"target": "ESNext","useDefineForClassFields": true,"module": "ESNext","moduleResolution": "Node","strict": true,"jsx": "preserve","resolveJsonModule": true,"isolatedModules": true,"esModuleInterop": true,"lib": ["ESNext", "DOM"],"skipLibCheck": true,"noEmit": true,"baseUrl": "." /* 用于设置基础 url,可以帮我们省掉一些多余的路径前缀。 */,"paths": {"/@/*": ["src/*"]} /* 将导入重新映射到相对于“baseUrl”的查找位置的一系列条目。 */,},"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],"references": [{ "path": "./tsconfig.node.json" }]}
新建一个全局的ts文件声明.vue后缀文件

npm i sass -D

npm i vue-router

创建文件 src/router/route.ts
import { RouteRecordRaw } from 'vue-router';/*** 建议:路由 path 路径与文件夹名称相同,找文件可浏览器地址找,方便定位文件位置** 路由meta对象参数说明* meta: {* title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化)* isLink: 是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空 2、isIframe:false`* isHide: 是否隐藏此路由* isKeepAlive: 是否缓存组件状态* isAffix: 是否固定在 tagsView 栏上* isIframe: 是否内嵌窗口,开启条件,`1、isIframe:true 2、isLink:链接地址不为空`* roles: 当前路由权限标识,取角色管理。控制路由显示、隐藏。超级管理员:admin 普通角色:common* icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx`* }*/// 扩展 RouteMeta 接口
declare module 'vue-router' {interface RouteMeta {title?: string;isLink?: string;isHide?: boolean;isKeepAlive?: boolean;isAffix?: boolean;isIframe?: boolean;roles?: string[];icon?: string;}
}/*** 定义动态路由* 前端添加路由,请在顶级节点的 `children 数组` 里添加* @description 未开启 isRequestRoutes 为 true 时使用(前端控制路由),开启时第一个顶级 children 的路由将被替换成接口请求回来的路由数据* @description 各字段请查看 `/@/views/system/menu/component/addMenu.vue 下的 ruleForm`* @returns 返回路由菜单数据*/
export const dynamicRoutes: Array = [{path: '/',name: '/',component: () => import('/@/layout/index.vue'),redirect: '/home',meta: {isKeepAlive: true,},children: [{path: '/home',name: 'home',component: () => import('/@/views/home/index.vue'),meta: {title: 'message.router.home',isLink: '',isHide: false,isKeepAlive: true,isAffix: true,isIframe: false,roles: ['admin', 'common'],icon: 'iconfont icon-shouye',},},],},
];/*** 定义404、401界面* @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify*/
export const notFoundAndNoPower = [{path: '/:path(.*)*',name: 'notFound',component: () => import('/@/views/error/404.vue'),meta: {title: 'message.staticRoutes.notFound',isHide: true,},},{path: '/401',name: 'noPower',component: () => import('/@/views/error/401.vue'),meta: {title: 'message.staticRoutes.noPower',isHide: true,},},
];/*** 定义静态路由(默认路由)* 此路由不要动,前端添加路由的话,请在 `dynamicRoutes 数组` 中添加* @description 前端控制直接改 dynamicRoutes 中的路由,后端控制不需要修改,请求接口路由数据时,会覆盖 dynamicRoutes 第一个顶级 children 的内容(全屏,不包含 layout 中的路由出口)* @returns 返回路由菜单数据*/
export const staticRoutes: Array = [{path: '/login',name: 'login',component: () => import('/@/views/login/index.vue'),meta: {title: '登录',},},
];
创建文件src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { staticRoutes, notFoundAndNoPower } from '/@/router/route';/*** 创建一个可以被 Vue 应用程序使用的路由实例* @method createRouter(options: RouterOptions): Router* @link 参考:https://next.router.vuejs.org/zh/api/#createrouter*/
export const router = createRouter({history: createWebHashHistory(),/*** 说明:* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。* 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示*/routes: [...notFoundAndNoPower, ...staticRoutes],
});// 导出路由
export default router;
使用:在src/main.ts中导入并注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'createApp(App).use(router).mount('#app')
npm install pinia

创建文件 src/stores/index.ts
// https://pinia.vuejs.org/
import { createPinia } from 'pinia';// 创建
const pinia = createPinia();// 导出
export default pinia;
创建文件 src/stores/userInfo.ts
import { defineStore } from 'pinia';
import Cookies from 'js-cookie';
import { Session } from '/@/utils/storage';/*** 用户信息* @methods setUserInfos 设置用户信息*/
export const useUserInfo = defineStore('userInfo', {state: (): UserInfosState => ({userInfos: {userName: '',photo: '',time: 0,roles: [],authBtnList: [],},}),actions: {async setUserInfos() {// 存储用户信息到浏览器缓存if (Session.get('userInfo')) {this.userInfos = Session.get('userInfo');} else {const userInfos: any = await this.getApiUserInfo();this.userInfos = userInfos;}},// 模拟接口数据async getApiUserInfo() {return new Promise((resolve) => {setTimeout(() => {// 模拟数据,请求接口时,记得删除多余代码及对应依赖的引入const userName = Cookies.get('userName');// 模拟数据let defaultRoles: Array = [];let defaultAuthBtnList: Array = [];// admin 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏let adminRoles: Array = ['admin'];// admin 按钮权限标识let adminAuthBtnList: Array = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];// test 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏let testRoles: Array = ['common'];// test 按钮权限标识let testAuthBtnList: Array = ['btn.add', 'btn.link'];// 不同用户模拟不同的用户权限if (userName === 'admin') {defaultRoles = adminRoles;defaultAuthBtnList = adminAuthBtnList;} else {defaultRoles = testRoles;defaultAuthBtnList = testAuthBtnList;}// 用户信息模拟数据const userInfos = {userName: userName,photo: 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500',time: new Date().getTime(),roles: defaultRoles,authBtnList: defaultAuthBtnList,};resolve(userInfos);}, 0);});},},
});
使用:在 src/main.ts中导入并注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from '/@/stores/index'createApp(App).use(pinia).use(router).mount('#app')
npm i axios

创建文件 src/utils/request.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Session } from '/@/utils/storage'
import qs from 'qs'// 配置新建一个 axios 实例
const service: AxiosInstance = axios.create({baseURL: import.meta.env.VITE_API_URL,timeout: 50000,headers: { 'Content-Type': 'application/json' },paramsSerializer: {serialize(params) {return qs.stringify(params, { allowDots: true })},},
})// 添加请求拦截器
service.interceptors.request.use((config: AxiosRequestConfig) => {// 在发送请求之前做些什么 tokenif (Session.get('token')) {config.headers!['Authorization'] = `${Session.get('token')}`}return config},(error) => {// 对请求错误做些什么return Promise.reject(error)}
)// 添加响应拦截器
service.interceptors.response.use((response) => {// 对响应数据做点什么const res = response.dataif (res.code && res.code !== 0) {// `token` 过期或者账号已在别处登录if (res.code === 401 || res.code === 4001) {Session.clear() // 清除浏览器全部临时缓存window.location.href = '/' // 去登录页ElMessageBox.alert('你已被登出,请重新登录', '提示', {}).then(() => {}).catch(() => {})}return Promise.reject(service.interceptors.response)} else {return response.data}},(error) => {// 对响应错误做点什么if (error.message.indexOf('timeout') != -1) {ElMessage.error('网络超时')} else if (error.message == 'Network Error') {ElMessage.error('网络连接错误')} else {if (error.response.data) ElMessage.error(error.response.statusText)else ElMessage.error('接口路径找不到')}return Promise.reject(error);}
);// 导出 axios 实例
export default service
npm i element-plus --save
使用:在 src/main.ts中导入并注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from '/@/stores/index'import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'createApp(App).use(pinia).use(router).use(ElementPlus).mount('#app')