VueCLI 项目初始化配置(Vue3+TS)
VueCLI 项目初始化配置(Vue3+TS)
全局环境
- Node.js 版本 v14.15.4
- Vue CLI 版本@vue/cli 4.5.13
- TypeScript 版本 Version 4.2.3
项目创建
- 执行命令
vue create project_name
2.选择初始化配置项 选择 vue3.x 版本
总体项目结构
ts_project
├─ .browserslistrc
├─ .env.development // 开发环境配置文件
├─ .env.production // 生产环境配置文件
├─ .env.test // 测试环境配置文件
├─ .eslintrc.js
├─ .gitignore
├─ babel.config.js
├─ package-lock.json
├─ package.json
├─ public
│ ├─ favicon.ico
│ └─ index.html
├─ README.md
├─ src
│ ├─ api
│ │ ├─ ajaxUrl.config.ts // 统一管理项目请求URL
│ │ ├─ login.ts // 退出和登录方法封装,目前仅封装了退出登录
│ │ └─ manage.ts // 封装请求方法
│ ├─ App.vue
│ ├─ assets
│ │ ├─ scss // 全局css样式
│ | │ └─ index.vue
│ │ └─ logo.png
│ ├─ components
│ │ └─ NoFind.vue // 定义 404 页面
│ ├─ main.ts // 入口
│ ├─ router // 配置路由
│ │ └─ index.ts
│ ├─ shims-vue.d.ts
│ ├─ store
│ │ └─ index.ts // Vuex 存储token 和用户信息等
│ ├─ utils // 工具函数等
│ │ ├─ request.ts // 请求拦截
│ │ └─ storage.ts // 封装 sessionStorage、localStorage、cookie 存储删除等
│ └─ views
│ ├─ Home // 内容页
│ │ └─ index.vue
│ ├─ Layout // 项目 Layout 布局
│ │ └─ index.vue
│ └─ Login // 登录页
│ └─ index.vue
├─ tsconfig.json
├─ vue.config.js //可选配置项
└─ yarn.lock
UI 库的安装和引入
安装 yarn add ant-design-vue
ant-design-vue 官网提供了两种引入方式一种是按需加载一种是全局引入,这里采用全局引入。安装完成后在main.ts
文件添加如下代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入ant-design-vue
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
createApp(App).use(store).use(Antd).use(router).mount('#app')
Axios 请求封装
安装
yarn add axios
请求拦截
注意:涉及到 token 的存储、vuex 存储 token 在对应的章节进行描述。request.ts
文件只是使用 在项目src
文件夹下创建文件夹 utils
文件夹 ,并创建 request.ts
文件,文件内容如下:
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
import { notification } from 'ant-design-vue' // 结合 ant-design-vue 做全局提示
import store from '@/store' // 获取 token
import { localCache } from '@/utils/storage' // 引入读取token
import { logout } from '@/api/login' // 引入退出登录方法
// 初始化
const instance = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 120 * 1000,
withCredentials: true
})
// 请求错误
const err = (error: {
message: string | string[],
response: { data: any, status: number }
}) => {
if (error.message.includes('timeout')) {
notification.error({
message: '系统提示',
description: '请求超时',
duration: 4
})
}
if (error.response) {
const data = error.response.data
const token = localCache.getCache('token')
if (error.response.status == 403) {
notification.error({
message: '系统提示',
description: '请求资源失败',
duration: 4
})
}
if (
error.response.status === 401 &&
!(data.result && data.result.isLogin)
) {
notification.error({
message: '系统提示',
description: '没有访问权限',
duration: 4
})
// token 存在但是没有访问权限,退出登录
if (token) {
logout()
}
}
}
return Promise.reject(error)
}
// 请求
instance.interceptors.request.use((config: AxiosRequestConfig) => {
// 获取系统token
const token: string = store.state.token
if (token) {
config.headers['X-Access-Token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
}
// 配置 get 接口默认参数携带时间戳请求
if (config.method == 'get') {
config.params = {
_t: new Date().getTime(),
...config.params
}
}
return config
}, err)
// 拦截成功请求
instance.interceptors.response.use((response: AxiosResponse) => {
const config: AxiosRequestConfig = response.config || ''
const code = Number(response.data.code)
// code 状态根据前后端协定接口成功状态修改
if (code == 200 || code == 0) {
if (config) {
console.log('请求成功')
}
return response.data
} else {
const errorCode = [402, 403, 500]
if (errorCode.includes(response.data.code)) {
notification.error({
message: '系统提示',
description: '没有权限',
duration: 4
})
setTimeout(() => {
window.location.reload()
}, 500)
}
}
}, err)
export default instance
说明:初始化里面的baseURL
的值直接读取 p
,值的配置项来源于.env
环境变量。其中.env
所有环境都会被载入,.env.test
测试环境被载入,.env.development
开发环境被载入,.env.production
生产环境被载入。文件的创建在项目的根目录和vue.config.js
同级。具体配置项可参考Vue CLI 模式和环境变量
封装
在项目src
文件夹下创建文件夹 api
文件夹 ,并创建 manage.ts
文件,文件内容如下:
import axios from '@/utils/request'
/**
* @desc post请求
* @param url 请求路径
* @param parameter 请求参数
* */
export function postAction(url: string, parameter: any) {
return axios({
url: url,
method: 'post',
data: parameter
})
}
/**
* @desc http请求
* @param url 请求路径
* @param parameter 请求参数
* @param method= {post | put}
* */
export function httpAction(url: string, parameter: any, method: any) {
return axios({
url: url,
method: method,
data: parameter
})
}
/**
* @desc put请求
* @param url 请求路径
* @param parameter 请求参数
* */
export function putAction(url: string, parameter: any) {
return axios({
url: url,
method: 'put',
data: parameter
})
}
/**
* @desc get请求
* @param url 请求路径
* @param parameter 请求参数
* */
export function getAction(url: string, parameter: any) {
return axios({
url: url,
method: 'get',
params: parameter
})
}
/**
* @desc delete请求
* @param url 请求路径
* @param parameter 请求参数
* */
export function deleteAction(url: string, parameter: any) {
return axios({
url: url,
method: 'delete',
params: parameter
})
}
vue.config.js 配置代理
- 在项目根目录创建
vue.config.js
配置服务器请求代理
module.exports = {
devServer: {
port: 3082,
proxy: {
'/pr-api': {
target: 'http://localhost:3085',
ws: false,
changeOrigin: true
}
}
},
lintOnSave: undefined
}
统一管理项目请求 API
- 在项目
src
文件夹下创建文件夹api
文件夹 ,并创建ajaxUrl.config.ts
文件,用于统一管理项目请求 API。示例如下
const Login = {
login: '/sys/login'
}
export { Login }
登录、退出
退出登录公共方法
在项目src
文件夹下创建文件夹 api
文件夹 ,并创建 login.ts
文件,管理退出登录。
import { createVNode } from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
// import { useRouter } from "vue-router";
import { postAction } from '@/api/manage'
import { cookies, localCache } from '@/utils/storage'
import { Modal, message } from 'ant-design-vue'
/*
退出
*/
export function logout() {
// const router = useRouter();
Modal.confirm({
title: '退出登录?',
icon: createVNode(ExclamationCircleOutlined),
okText: '确认',
cancelText: '取消',
onOk() {
postAction('/sys/logout', {}).then((res: any) => {
if (res.success) {
// 清空浏览器存储的所有数据
cookies.removeCookie('vuex')
localCache.clearCache()
// 提示退出成功
message.success(res.message)
// 跳转到登录页
// router.push({ name: "Login" });
// 刷新整个浏览器
setTimeout(() => {
window.location.reload()
}, 100)
}
})
},
onCancel() {
message.info('取消退出登录')
},
class: 'test'
})
}
缓存存储读取封装
注意:js-cookie 的安装查看vuex-persistedstate 使用步骤章节的3.安装 js-cookie
在项目src
文件夹下创建文件夹 utils
文件夹 ,并创建 storage.ts
文件,文件内容如下:
import Cookies from 'js-cookie'
/*
* localStorage 封装
*/
const localCache = {
// 设置
setCache(key: string, value: any) {
window.localStorage.setItem(key, JSON.stringify(value))
},
// 获取
getCache(key: string) {
const value = window.localStorage.getItem(key)
if (value) {
return JSON.parse(value)
}
},
// 清空某一个
deleteCache(key: string) {
window.localStorage.removeItem(key)
},
// 清空全部
clearCache() {
window.localStorage.clear()
}
}
/**
* sessionStorage 分装
*/
const sessionStorage = {
//存储
set(key: string, value: any) {
window.sessionStorage.setItem(key, JSON.stringify(value))
},
//取出数据
get<T>(key: string) {
const value = window.sessionStorage.getItem(key)
if (value && value != 'undefined' && value != 'null') {
return JSON.parse(value)
}
return null
},
// 删除数据
remove(key: string) {
window.sessionStorage.removeItem(key)
}
}
const cookies = {
getCookie(key: string) {
return Cookies.get(key)
},
setCookie(key: string, value: any) {
Cookies.set(key, value)
},
removeCookie(key: string) {
Cookies.remove(key)
return
}
}
export { sessionStorage, localCache, cookies }
登录
注意:涉及到路由、vuex 存储 token 在对应的章节进行描述。
<template>
<a-form layout="horizontal" :model="formState">
<a-form-item label="账号">
<a-input v-model:value="formState.username" placeholder="请输入账号" />
</a-form-item>
<a-form-item label="密码">
<a-input
v-model:value="formState.password"
type="password"
placeholder="请输入密码"
/>
</a-form-item>
<a-button type="primary" @click="onSubmit">登录</a-button>
</a-form>
</template>
<script lang="ts">
import { defineComponent, reactive, UnwrapRef, toRaw } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
// 引入 封装的请求方法
import { postAction } from '@/api/manage'
// 引入 URL 统一管理文件
import { Login } from '@/api/ajaxUrl.config'
// 声明接口
interface FormState {
username: string
password: string | number
}
export default defineComponent({
setup() {
const formState: UnwrapRef<FormState> = reactive({
username: '',
password: ''
})
// Vuex
const store = useStore()
// Vue Router
const router = useRouter()
// 提交表单
const onSubmit = () => {
let params = toRaw(formState)
postAction(Login.login, params).then((res: any) => {
if (res.success) {
// 将token数据设置到Vuex
store.commit('setToken', res.result.token)
// 设置用户信息到Vuex
store.commit('setUserInfo', res.result.userInfo)
// 登录成功
router.push({ name: 'Layout' })
} else {
message.error(res.message)
}
})
}
return {
formState,
onSubmit
}
}
})
</script>
退出
<template>
<div>
<a-button type="primary" @click="outLogin">退出登录</a-button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { logout } from '@/api/login'
export default defineComponent({
setup() {
// 退出登录
const outLogin = () => {
logout()
}
return {
outLogin
}
}
})
</script>
Layout 布局
Layout 布局
- 修改
App.vue
文件
<template>
<!-- 设置路由出口 -->
<router-view />
</template>
<style lang="scss"></style>
- 路由跳转和
layout
布局内容渲染
<template>
<a-layout>
<!-- 头部导航 -->
<a-layout-header class="header">
<div class="logo" />
<a-menu
theme="dark"
mode="horizontal"
v-model:selectedKeys="selectedKeys1"
:style="{ lineHeight: '64px' }"
>
<a-menu-item key="1">nav 1</a-menu-item>
</a-menu>
</a-layout-header>
<a-layout>
<!-- 左侧菜单 -->
<a-layout-sider width="200" style="background: #fff">
<a-menu
mode="inline"
v-model:selectedKeys="selectedKeys2"
v-model:openKeys="openKeys"
:style="{ height: '100%', borderRight: 0 }"
>
<a-sub-menu key="sub1">
<template #title>
<span>
<user-outlined />
用户信息
</span>
</template>
<a-menu-item key="/home">
<router-link :to="{ path: 'home' }">
<span>home</span>
</router-link>
</a-menu-item>
<a-menu-item key="2">option2</a-menu-item>
</a-sub-menu>
</a-menu>
</a-layout-sider>
<!-- 中心区域 -->
<a-layout style="padding: 0 24px 24px">
<!-- 面包屑 -->
<a-breadcrumb style="margin: 16px 0">
<a-breadcrumb-item>Home</a-breadcrumb-item>
</a-breadcrumb>
<a-layout-content
:style="{
background: '#fff',
padding: '24px',
margin: 0,
minHeight: '280px'
}"
>
<!-- 设置路由出口 -->
<router-view />
</a-layout-content>
</a-layout>
</a-layout>
</a-layout>
</template>
<script lang="ts">
import { UserOutlined } from '@ant-design/icons-vue'
import { defineComponent, ref } from 'vue'
export default defineComponent({
components: {
UserOutlined
},
setup() {
return {
selectedKeys1: ref<string[]>(['2']),
selectedKeys2: ref<string[]>(['1']),
collapsed: ref<boolean>(false),
openKeys: ref<string[]>(['sub1'])
}
}
})
</script>
<style>
#components-layout-demo-top-side-2 .logo {
float: left;
width: 120px;
height: 31px;
margin: 16px 24px 16px 0;
background: rgba(255, 255, 255, 0.3);
}
.ant-row-rtl #components-layout-demo-top-side-2 .logo {
float: right;
margin: 16px 0 16px 24px;
}
.site-layout-background {
background: #fff;
}
</style>
VueX 共享数据
由于 Vuex
需要存储全局的 token
和用户信息,由于 Vuex 是运行在内存中,数据也存储在内存中,用户刷新页面操作时,内存数据会重新进行初始化 通过 sessionStorage
/ localStorage
/ cookie
来进行数据的持久化存储。这里我们使用了两个插件实现 Vuex 数据的持久化。
Vuex 数据持久化插件vuex-persistedstate结合js-cookie 进行 Vuex 状态持久化的设置获取移除。
注:ts 项目使用 js-cookie 需要安装 "js-cookie": "^3.0.1", 和 "@types/js-cookie": "^2.2.7", 两个依赖包
vuex-persistedstate 使用步骤
1.安装插件
yarn add vuex-persistedstate -S
2.使用插件
export default createStore({
getters,
// 使用插件
plugins: [createPersistedState()]
})
3.安装 js-cookie
yarn add js-cookie@3.0.1 @types/js-cookie@2.2.7
具体使用
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import Cookies from 'js-cookie'
import {localCache} from '@/utils/storage'
// 定义用户状态接口
export interface UserState {
count: number
token: string,
userInfo: any,
}
// vuex-persistedstate提供有一个reducer函数,可以自定义存储Key,或者使用paths参数,建议使用paths参数比较简单
// 非Module格式:xxxx
// 使用了Module的格式:ModuleName.xxxx,这里持久化的是Theme Module下面的persistData数据
const PERSIST_PATHS = ['token', 'userInfo']
// 初始化Vuex
export default createStore({
state:<UserState> {
count: 0,
token: '',
userInfo: {}
},
mutations: {
add(state){
state.count ++
},
// 设置token
setToken(state, token) {
state.token = token;
localCache.setCache('token', token)
},
// 设置用户信息
setUserInfo(state, userInfo) {
state.userInfo = userInfo;
localCache.setCache('userInfo', userInfo)
}
},
actions: {
},
modules: {
},
getters: {
},
// 使用Vuex 数据持久化插件
plugins: [createPersistedState({
/*
storage 默认存储到 localStorage
存储到 sessionStorage 配置 storage: window.sessionStorage
*/
// reducer: function 返回需要储存的state对象
// reducer(val) {
// return {
// // 持久化存储 state 中的 token
// token: val.token,
// userInfo: val.userInfo
// }
// },
/*
paths 设置保留持久化的数据,不设置则持久化全部数据
*/
paths: PERSIST_PATHS,
storage: {
getItem: (key) => Cookies.get(key),
// Please see https://github.com/js-cookie/js-cookie#json, on how to handle JSON.
setItem: (key, value) => Cookies.set(key, value, { expires: 3, secure: true }),
removeItem: (key) => Cookies.remove(key),
},
})]
})
VueRouter 使用
嵌套路由 Layout 布局
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 路由懒加载
const Login = () => import('@/views/Login/index.vue')
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Login',
component: Login
},
{
path: '/layout',
name: 'Layout',
component: () => import('@/views/Layout/index.vue'),
// 定义嵌套路由 实现 Layout 布局
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home/index.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
}
]
},
{
// 匹配所有路径 vue2使用* vue3使用/:pathMatch(.*)*或/:pathMatch(.*)或/:catchAll(.*)
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/components/NoFind.vue')
}
]
// 初始化路由
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
路由拦截
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 引入获取token 方法
import { localCache } from '@/utils/storage'
// 路由懒加载
const Login = () => import('@/views/Login/index.vue')
// 配置路由
const routes: Array<RouteRecordRaw> = []
// 初始化路由
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// 设置路由守卫
router.beforeEach((to, from, next) => {
// to表示将要访问的路径,form表示从那个页面跳转而来,next表示允许跳转到指定位置
if (to.path == '/') {
// 当前访问为登陆页,直接进入
next()
} else {
// 获取用户本地的token, 如果token不存在则跳转到登录页
const token = localCache.getCache('token')
if (!token) {
next('/')
} else {
// 如果登录了,则直接跳转
next()
}
}
})
export default router
整体代码
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 引入获取token 方法
import { localCache } from '@/utils/storage'
// 路由懒加载
const Login = () => import('@/views/Login/index.vue')
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Login',
component: Login
},
{
path: '/layout',
name: 'Layout',
component: () => import('@/views/Layout/index.vue'),
// 定义嵌套路由 实现 Layout 布局
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home/index.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
}
]
},
{
// 匹配所有路径 vue2使用* vue3使用/:pathMatch(.*)*或/:pathMatch(.*)或/:catchAll(.*)
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/components/NoFind.vue')
}
]
// 初始化路由
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// 设置路由守卫
router.beforeEach((to, from, next) => {
// to表示将要访问的路径,form表示从那个页面跳转而来,next表示允许跳转到指定位置
if (to.path == '/') {
// 当前访问为登陆页,直接进入
next()
} else {
// 获取用户本地的token, 如果token不存在则跳转到登录页
const token = localCache.getCache('token')
if (!token) {
next('/')
} else {
// 如果登录了,则直接跳转
next()
}
}
})
export default router
配置全局样式
结合 scss 和 vue.config.js 进行全局样式配置,scss 预处理在项目创建初始化配置项已经安装。 在src
文件夹下的assets
文件夹下建scss
文件夹并创建index.scss
文件,在vue.config.js
中配置。参考项目 vue.config 配置中配置 scss
项目 vue.config 配置
在 vue.config.js 进行配置
module.exports = {
configureWebpack: (config) => {
//生产环境取消 console.log
if (process.env.NODE_ENV === "production") {
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
} else {
// 开发环境
}
},
// 配置 scss
css: {
loaderOptions: {
scss: {
prependData: `@import "@/assets/scss/index.scss";`,
},
},
},
// 配置服务端代理
devServer: {
port: 3082,
proxy: {
"/pr-api": {
target: "http://localhost:3085",
ws: false,
changeOrigin: true,
},
},
},
lintOnSave: undefined,
};