文章目录
一、项目概述
该项目是一个基于 Vite + Vue3 + TS 的项目模板,目的是为了节省每次开发新项目用于配置环境的时间,并附有简单的登录、注册及导航页。
项目集成:
项目启动
项目地址:
- github:https://github.com/ljh-coder/vite-vue3-ts-template
- gitee: https://gitee.com/ljh210107/vite-vue3-ts-template
拉取项目并安装相关依赖包(此处以 gitee
为例):
git clone https://gitee.com/ljh210107/vite-vue3-ts-template.git
cd vite-vue3-ts-template
yarn install
启动项目(登录、注册功能需要自行搭建后端配合使用):
yarn run dev
项目演示
内附简单的登录、注册及导航页,需要自行搭建后端配合使用:
二、创建项目
使用 yarn
创建项目:
yarn create vite vite-vue3-ts-template --template vue-ts
此处选用 vue-ts
模板,项目名称为:vite-vue3-ts-template
。
安装相关依赖并启动项目:
cd vite-vue3-ts-template
yarn install
yarn run dev
三、配置路径别名
为了使用 node 相关模块,可安装:
yarn add @types/node -D
打开 vite.config.ts
文件,添加路径别名及启动端口的配置:
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: "0.0.0.0",
port: 5173,
},
resolve: {
alias: {
// 配置文件路径别名
"@": resolve(__dirname, "src"),
},
},
});
四、配置路由
安装 vue-router
,默认安装 4.x
版本:
yarn add vue-router
在 src
目录下创建文件夹 router
,并创建 index.ts
文件:
// src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: () => import('@/views/home/Home.vue'),
},
{
path: '/login',
component: () => import('@/views/login/Login.vue'),
},
]
})
export default router;
在 src
目录下创建文件夹 views
,并在该文件夹内创建 home
和 login
文件夹,分别在文件夹内创建 Home.vue
和 Login.vue
文件。
可能会出现报错:找不到模块“@/views/home/Home.vue”或其相应的类型声明
。
此时只需在 vite-env.d.ts
文件中添加以下内容即可:
// vite-env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const componentOptions: ComponentOptions
export default componentOptions
}
在 main.ts
中挂载路由配置:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from '@/router/index'
const app = createApp(App)
// 挂载路由配置
app.use(router)
app.mount('#app')
可能出现报错:找不到模块“@/router/index”或其相应的类型声明。
此时需要在 tsconfig.app.json
文件中配置路径别名即可:
{
"compilerOptions": {
/* Bundler mode */
...
/* Linting */
...
// 设置基础路径
"baseUrl": ".",
// 路径别名配置
"paths": { "@/*": ["src/*"] },
}
}
注意: 可能需要重启 IDE 才能生效。
修改 App.vue
文件为以下内容,测试路由:
<template>
<router-link to="/home"> Home </router-link>
<router-link to="/login"> Login </router-link>
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<style scoped></style>
Vue Router 参考资料:https://router.vuejs.org/zh/introduction.html
五、引入状态管理工具 Pinia
安装 pinia
用于跨组件共享状态, pinia-plugin-persistedstate
则用于数据的持久化:
yarn add pinia pinia-plugin-persistedstate
在 main.ts
文件中引入:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 状态管理工具 Pinia
import { createPinia } from 'pinia'
import persistedstate from 'pinia-plugin-persistedstate'
// -----------------
const app = createApp(App)
// 创建 pinia 实例
const pinia = createPinia()
// 使用持久化存储插件
pinia.use(persistedstate)
app.use(pinia)
app.mount('#app')
在 src
目录下创建文件夹 stores
,并创建文件 user.ts
:
// src/stores/user.ts
import { defineStore } from "pinia";
import { ref } from "vue";
interface User {
// 用户ID
id: number
// 用户名称
username: string
// 登录凭证
token: string
}
export const useUserStore = defineStore("user", () => {
// 定义用户信息
const userInfo = ref<User>();
// 保存用户信息
const setUserInfo = (val: User) => {
userInfo.value = val
}
// 清空用户信息
const clearUserInfo = () => {
userInfo.value = undefined
}
return {
userInfo,
setUserInfo,
clearUserInfo
}
},{
// 数据持久化
persist: {
// 默认存储至 localStorage
storage: localStorage,
// 指定需要被持久化的数据
paths: ["userInfo"],
}
});
在 App.vue
中使用:
<template>
<!-- 路由测试 -->
<router-link to="/home"> Home </router-link>
<router-link to="/login"> Login </router-link>
<router-view></router-view>
</template>
<script setup lang="ts">
import { useUserStore } from "@/stores/user";
import { onMounted } from "vue";
const userStore = useUserStore();
onMounted(() => {
userStore.setUserInfo({
id: 1,
username: "tom",
token: "123",
});
console.log(userStore.userInfo);
});
</script>
查看数据是否持久化:
Pinia 参考资料:https://pinia.web3doc.top/introduction.html
pinia-plugin-persistedstate 参考资料:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
六、引入 UI 框架 Element-plus
安装 element-plus
:
yarn add element-plus
安装 unplugin-vue-components
和 unplugin-auto-import
这两款插件,以实现按需自动导入:
yarn add unplugin-vue-components unplugin-auto-import -D
修改 vite.config.ts
配置文件如下:
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
添加全局配置:
// src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
// -----------------
const app = createApp(App)
...
app.use(ElementPlus, { size: 'small', zIndex: 3000, locale: zhCn })
app.mount('#app')
注: size
用于设置表单组件的默认尺寸,zIndex
用于设置弹出组件的层级,locale
用于设置组件文字的默认语言。
Element-plus 参考资料:https://element-plus.org/zh-CN/guide/installation.html
七、添加全局接口配置文件
在根目录下分别创建 .env.development
(开发环境) 、.env.production
(生产环境)、.env.test
(测试环境)文件。
.env.development
开发环境配置文件:
# .env.development 开发环境配置文件
NODE_ENV = 'development'
VITE_ENV = 'development'
VITE_APP_BASE_API = '/api'
VITE_SERVER = 'http://127.0.0.1:3000'
.env.production
生产环境配置文件:
# .env.production 生产环境配置文件
NODE_ENV = 'production'
VITE_ENV = 'production'
VITE_APP_BASE_API = 'http://xx.xx.xx.xx'
VITE_SERVER = 'http://xx.xx.xx.xx'
.env.test
测试环境配置文件:
# .env.test 测试环境配置文件
NODE_ENV = 'test'
VITE_ENV = 'test'
VITE_APP_BASE_API = 'http://xx.xx.xx.xx'
VITE_SERVER = 'http://xx.xx.xx.xx'
修改 package.json
文件中的 scripts
,根据不同命令打包不同环境项目:
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"build:production": "vue-tsc && vite build --mode production",
"build:test": "vue-tsc && vite build --mode test",
"preview": "vite preview"
}
八、集成 Axios
安装 axios
:
yarn add axios
在 src
目录下新建 utils
文件夹,并新建 request.ts
文件,用于封装 axios
:
import axios, { InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig } from "axios";
import { useUserStore } from "@/stores/user";
import { ElNotification } from "element-plus";
// import { useRouter } from "vue-router"
// 由于 useRouter 必须写在 setup 中,所有无法在此使用
import Vrouter from "@/router"
// const route = Vrouter.currentRoute.value
const router = Vrouter
const http = axios.create({
// 根据全局的环境变量来设置请求 api 的 baseURL,例如在 development (开发环境),import.meta.env.VITE_APP_BASE_API 的值 => '/api'
// 注意:如果 VITE_APP_BASE_API 的值是一个相对地址,如:'/api',则请求会经过 server.proxy 代理拼接完整路径。
// 如果 VITE_APP_BASE_API 的值是完整的地址,如:http://xx.xx.xx.xx/,则会直接发送请求而不经过 server.proxy 代理
baseURL: import.meta.env.VITE_APP_BASE_API,
// 请求超时时间
timeout: 5000,
});
// 请求拦截
http.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 获取用户信息中的 token 信息
const userStore = useUserStore();
const token = userStore.userInfo?.token;
if (token) {
// 添加 token 请求头标识(注意 Bearer 后面还有一个空格!!!)
config.headers.Authorization = 'Bearer ' + token;
}
return config;
},
(error) => {
console.log(error);
ElNotification({
title: "Error",
message: error.message,
type: "error",
});
return Promise.reject(error);
}
);
// 响应拦截
http.interceptors.response.use(
(response: AxiosResponse) => {
// 请求成功
// 处理成功响应
if (response.status >= 200 && response.status < 300) {
return response;
} else {
ElNotification({
title: "Error",
message: "Request error.",
type: "error",
});
return Promise.reject(new Error("*Request error."));
}
},
(error) => {
// 请求失败
console.log(error);
// 状态提示
let msg = "";
let status = error.response.status;
console.log(status);
switch (status) {
// token 过期处理
case 401:
msg = "Login expired, please log in again.";
// 清理用户数据,跳转至登录页面
const userStore = useUserStore();
userStore.clearUserInfo();
// 跳转至登录页面
router.push("/login");
break;
case 404:
msg = "The network request does not exist.";
break;
case 500:
msg = "Server error.";
break;
default:
msg = error.response.data.message;
break;
}
ElNotification({
title: "Error",
message: msg,
type: "error",
});
return Promise.reject(error);
}
);
// 后端数据响应格式
type ResponseResult<T> = {
success: boolean;
code: number;
msg: string;
data: T;
};
// 封装 http 请求函数
const httpRequest = async <T>(config: AxiosRequestConfig): Promise<ResponseResult<T>> => {
const response: AxiosResponse<ResponseResult<T>> = await http.request(config);
// 返回整个 ResponseResult 对象
return response.data;
};
export default httpRequest;
配置代理以处理跨域问题,在 vite.config.ts
文件中添加配置:
// vite.config.ts
import { defineConfig, loadEnv } from "vite";
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
// 根据当前工作目录中的 `mode` 加载 .env 文件
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
const env = loadEnv(mode, process.cwd(), "");
return {
plugins: [
...
],
server: {
host: "0.0.0.0",
port: 5174,
// 配置代理 - 解决跨域问题
proxy: {
// 如果 env.VITE_APP_BASE_API 的值为相对地址,如:'/api',则会经过代理
[env.VITE_APP_BASE_API]: {
// 请求接口前缀
target: env.VITE_SERVER,
changeOrigin: true,
// 将请求中的相对路径(env.VITE_APP_BASE_API) 替换成空字符串,
// 例如:开发环境下 process.env.VITE_APP_BASE_API 的值如果为 '/api',
// 请求的路径为 '/user',拼接后则变成 target + '/user' => 127.0.0.1:3000/user
// 总结:实际上就是将请求接口的 '/api' 字符串去掉,当然也不一定是 '/api' 这个字符串,根据的是 env.VITE_APP_BASE_API 的值进行处理的
rewrite: (path) => path.replace(new RegExp('^' + (process.env.VITE_APP_BASE_API as string)), '')
},
},
},
resolve: {
...
},
};
});
注意:如果
VITE_APP_BASE_API
的值是一个相对地址,如:'/api'
,则请求会经过server.proxy
代理拼接完整路径。
如果VITE_APP_BASE_API
的值是完整的地址,如:http://xx.xx.xx.xx/
,则会直接发送请求而不经过server.proxy
代理
在 src
目录下新建 api
文件夹,并新建 user.ts
文件,定义请求接口:
// src/api/user.ts
import httpRequest from "@/utils/request";
// 登录成功后返回的结果
export type LoginResult = {
// 用户ID
id: number
// 用户名称
email: string
// 登录凭证
token: string
}
// 登录验证
export const userLoginAPI = (email: string, password: string) => {
return httpRequest<LoginResult>({
method: 'POST',
url: '/users/login',
data: {
email,
password
}
})
}
在 Login.vue
测试登录功能:
// src/views/login/Login.vue
<template>
<el-form class="form" :model="userForm" ref="userFormRef">
<div class="input-group">
<label for="email">Email</label>
<el-form-item prop="email">
<el-input type="text" v-model="userForm.email" placeholder="Enter your email" />
</el-form-item>
</div>
<div class="input-group">
<label for="password">Password</label>
<el-form-item prop="password">
<el-input type="password" v-model="userForm.password" show-password placeholder="Enter your password" />
</el-form-item>
</div>
<el-button class="sign" @click="login">Sign in</el-button>
</el-form>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import { userLoginAPI } from "@/api/user";
import { useRouter } from "vue-router";
import type { FormInstance } from "element-plus";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
type UserForm {
email: string;
password: string;
}
const userFormRef = ref<FormInstance>();
const userForm = reactive<UserForm>({
email: "",
password: "",
});
// 登录
const router = useRouter()
const login = async () => {
// 登录验证
const res = await userLoginAPI(userForm.email, userForm.password);
console.log(res);
if (res.success) {
// 保存用户信息
userStore.setUserInfo(res.data)
setTimeout(() => {
// 登录成功后跳转至首页
router.push('/home')
}, 3000);
}
};
</script>
参考链接:
- https://blog.csdn.net/chengcheng9876/article/details/140095182
- https://blog.csdn.net/ymzhaobth/article/details/130930243
- https://blog.csdn.net/ThisEqualThis/article/details/137853443
- https://cloud.tencent.com/developer/article/2399342
- https://developer.aliyun.com/article/1540140?spm=a2c6h.12873639.article-detail.10.75a41a76oSdqEg
- https://www.cnblogs.com/angia/p/18220779
- https://vitejs.cn/vite3-cn/config/server-options.html#server-open
九、集成 VueUse
VueUse
是基于合成 API 的实用函数集合,借助它可以快速实现一些常见的功能。
安装 VueUse
:
yarn add @vueuse/core
使用,此处以监听键盘事件为例:
import { onKeyStroke } from '@vueuse/core'
// 此处以 Backspace 键为例
onKeyStroke('Backspace', (e) => {
// 阻止默认事件,例如 Backspace 键的删除功能
// e.preventDefault()
console.log(1);
})
可将以上相关代码写入任意页面组件,当用户在页面中按下 Backspace
将触发相应事件。
更多方法参见:https://vueuse.nodejs.cn/functions.html
十、集成 Less
yarn add less -D
使用时只需修改 style
的 lang
即可:
<style lang="less">
@com_font_family: Comic Sans MS, Comic Sans, cursive, Arial, sans-serif;
.el-form {
font-family: @com_font_family;
&:hover {
background-color: #212121;
}
}
</style>