使用vite构建vue3项目详细介绍(ts+pinia+sass+vue-router+axios+element-plus)
1. 创建项目
npm init vite@latest
2. 配置 vite.config.ts
path需要安装--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<string, string> = {
'@': 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;
3. 配置 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后缀文件
4. 配置使用 Sass
npm i sass -D
5. 配置使用 vue-router
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<RouteRecordRaw> = [
{
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<RouteRecordRaw> = [
{
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')
6. 配置使用 Pinia
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<string> = [];
let defaultAuthBtnList: Array<string> = [];
// admin 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏
let adminRoles: Array<string> = ['admin'];
// admin 按钮权限标识
let adminAuthBtnList: Array<string> = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏
let testRoles: Array<string> = ['common'];
// test 按钮权限标识
let testAuthBtnList: Array<string> = ['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')
7. 配置使用 Axios
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) => {
// 在发送请求之前做些什么 token
if (Session.get('token')) {
config.headers!['Authorization'] = `${Session.get('token')}`
}
return config
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 添加响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
const res = response.data
if (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
8. 配置使用 elementplus
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')