初化化项目
pnpm create vite
2. 引入eslint和prettier,约束代码风格
安装 eslint
pnpm i -D eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin
这四个依赖的作用分别是:
eslint: EsLint的核心代码
eslint-plugin-vue:为Vue使用Eslint的插件
@typescript-eslint/parser:ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码
@typescript-eslint/eslint-plugin:这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规项目下新建 .eslintrc.js,配置 eslint 校验规则:
module.exports = {
extends: ["plugin:vue/vue3-essential"],
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
plugins: ["vue", "prettier"],
rules: {
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-var": "error",
"prettier/prettier": "error",
// 禁止出现console
"no-console": "warn",
// 禁用debugger
"no-debugger": "warn",
// 禁止出现重复的 case 标签
"no-duplicate-case": "warn",
// 禁止出现空语句块
"no-empty": "warn",
// 禁止不必要的括号
"no-extra-parens": "off",
// 禁止对 function 声明重新赋值
"no-func-assign": "warn",
// 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
"no-unreachable": "warn",
// 强制所有控制语句使用一致的括号风格
curly: "warn",
// 要求 switch 语句中有 default 分支
"default-case": "warn",
// 强制尽可能地使用点号
"dot-notation": "warn",
// 要求使用 === 和 !==
eqeqeq: "warn",
// 禁止 if 语句中 return 语句之后有 else 块
"no-else-return": "warn",
// 禁止出现空函数
"no-empty-function": "warn",
// 禁用不必要的嵌套块
"no-lone-blocks": "warn",
// 禁止使用多个空格
"no-multi-spaces": "warn",
// 禁止多次声明同一变量
"no-redeclare": "warn",
// 禁止在 return 语句中使用赋值语句
"no-return-assign": "warn",
// 禁用不必要的 return await
"no-return-await": "warn",
// 禁止自我赋值
"no-self-assign": "warn",
// 禁止自身比较
"no-self-compare": "warn",
// 禁止不必要的 catch 子句
"no-useless-catch": "warn",
// 禁止多余的 return 语句
"no-useless-return": "warn",
// 禁止变量声明与外层作用域的变量同名
"no-shadow": "off",
// 允许delete变量
"no-delete-var": "off",
// 强制数组方括号中使用一致的空格
"array-bracket-spacing": "warn",
// 强制在代码块中使用一致的大括号风格
"brace-style": "warn",
// 强制使用骆驼拼写法命名约定
camelcase: "warn",
// 强制使用一致的缩进
indent: "off",
// 强制在 JSX 属性中一致地使用双引号或单引号
// 'jsx-quotes': 'warn',
// 强制可嵌套的块的最大深度4
"max-depth": "warn",
// 强制最大行数 300
// "max-lines": ["warn", { "max": 1200 }],
// 强制函数最大代码行数 50
// 'max-lines-per-function': ['warn', { max: 70 }],
// 强制函数块最多允许的的语句数量20
"max-statements": ["warn", 100],
// 强制回调函数最大嵌套深度
"max-nested-callbacks": ["warn", 3],
// 强制函数定义中最多允许的参数数量
"max-params": ["warn", 3],
// 强制每一行中所允许的最大语句数量
"max-statements-per-line": ["warn", { max: 1 }],
// 要求方法链中每个调用都有一个换行符
"newline-per-chained-call": ["warn", { ignoreChainWithDepth: 3 }],
// 禁止 if 作为唯一的语句出现在 else 语句中
"no-lonely-if": "warn",
// 禁止空格和 tab 的混合缩进
"no-mixed-spaces-and-tabs": "warn",
// 禁止出现多行空行
"no-multiple-empty-lines": "warn",
// 强制在块之前使用一致的空格
"space-before-blocks": "warn",
// 强制在 function的左括号之前使用一致的空格
// 'space-before-function-paren': ['warn', 'never'],
// 强制在圆括号内使用一致的空格
"space-in-parens": "warn",
// 要求操作符周围有空格
"space-infix-ops": "warn",
// 强制在一元操作符前后使用一致的空格
"space-unary-ops": "warn",
// 强制在注释中 // 或 /* 使用一致的空格
// "spaced-comment": "warn",
// 强制在 switch 的冒号左右有空格
"switch-colon-spacing": "warn",
// 强制箭头函数的箭头前后使用一致的空格
"arrow-spacing": "warn",
"no-var": "warn",
"prefer-const": "warn",
"prefer-rest-params": "warn",
"no-useless-escape": "warn",
"no-irregular-whitespace": "warn",
"no-prototype-builtins": "warn",
"no-fallthrough": "warn",
"no-extra-boolean-cast": "warn",
"no-case-declarations": "warn",
"no-async-promise-executor": "warn",
},
overrides: [
{
files: ["*.vue"],
rules: {
// 这里写覆盖vue文件的规则
"no-unused-vars": [0],
},
},
],
};
3. 安装prettier
pnpm i --save-dev prettier eslint-config-prettier eslint-plugin-prettier
这三个依赖分别是:
prettier:prettier插件的核心代码
eslint-config-prettier:解决ESLint中的样式规范和prettier中样式规范的冲突,以prettier的样式规范为准,使ESLint中的样式规范自动失效
eslint-plugin-prettier:将prettier作为ESLint规范来使用
项目下新建 .prettier.js,配置 prettier 规则:
module.exports = {
printWidth: 120, // 换行字符串阈值
tabWidth: 2, // 设置工具每一个水平缩进的空格数
useTabs: false,
semi: false, // 句末是否加分号
vueIndentScriptAndStyle: true,
trailingComma: 'none', // 最后一个对象元素加逗号
bracketSpacing: true, // 对象,数组加空格
jsxBracketSameLine: true, // jsx > 是否另起一行
arrowParens: 'always', // (x) => {} 是否要有小括号
requirePragma: false, // 不需要写文件开头的 @prettier
insertPragma: false // 不需要自动在文件开头插入 @prettier
}
在 package.json 文件中添加 checklint 和 prettier 命令
在 package.json 文件中添加 checklint 和 prettier 命令
上面配置完成后,可以运行以下命令测试下代码检查 并执行格式化效果:
上面配置完成后,可以运行以下命令测试下代码检查 并执行格式化效果:
4.引入 Element Plus
安装 element-plus
pnpm install element-plus --save
修改入口文件 mian.ts :
import { createApp } from 'vue'
import App from './App.vue';
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
以上代码便完成了Element Plus的引入。需要注意的是,样式文件需要单独引入。
5. 引入路由
安装 router4
pnpm add vue-router
在 src 文件下新增 router 文件夹,新建index.ts,内容如下:
import { createRouter, createWebHistory, RouteRecordRaw } from'vue-router'constroutes: RouteRecordRaw[] = [
{
path: '/',
name: 'Login',
component: () =>import('@/pages/login/Login.vue'), // 注意这里要带上 文件后缀.vue
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
exportdefault router
复制代码
修改入口文件 mian.ts :
import { createApp } from'vue'importAppfrom'./App.vue'importElementPlusfrom'element-plus'import'element-plus/dist/index.css'import router from'./router/index'const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.mount('#app')
复制代码
6. 状态管理 pinia
安装 pinia
yarn add pinia@next复制代码
修改入口文件 mian.ts :
import { createApp } from'vue'importAppfrom'./App.vue'importElementPlusfrom'element-plus'import'element-plus/dist/index.css'import router from'./router/index'import { createPinia } from"pinia"const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.use(createPinia())
app.mount('#app')
复制代码
在 src 文件夹下新增 store 文件夹,接在在 store 中新增 main.ts,文件中定义:
import { defineStore } from'pinia'//`id` 是必要的,它将所使用 store 连接到 devtools。exportconst useMainStore = defineStore({
id: 'mian',
state: () => ({ name: '用户名' }),
getters: { name: (state) => state.name, },
actions: {
//同步actionupdataUser(data: any) {
this.name = data;
}
//异步actionasync updataUser2(data: any) {
const { data } = await api.login(account, pwd)
return data
}
}
})
复制代码
组件中使用:
<template><div>{{mainStore.name}}</div><button @click = 'handClick()'>{{'点击'}}</button></template><scriptsetuplang="ts">import { useMainStore } from"@/store/mian"//组件中获取storeconst mainStore = useMainStore()
//组件中通过getter获取store内容nameconst name = mainStore.name//可通过getter直接修改store中的name,推荐使用actions进行修改constupdateName = ()=>{
// 使用$patch 修改 store 中的数据
mainStore.$patch({
name: '修改的名称'
})
}
//可通过actions直接修改store中的name(同步)consthandClick=()=>{mainStore.name = '修改的名称'}
</script>复制代码
数据持久化
安装pinia-plugin-persist可以辅助实现数据持久化功能(当页面刷新的时候存储在pinia的内容会消失)。
npm i pinia-plugin-persist --save复制代码
在main.ts文件中定义定义pinia-plugin-persist使用
import { defineStore } from'pinia'import piniaPluginPersist from 'pinia-plugin-persist'const useMainStore = defineStore({
id: 'mian',
state: () => ({ name: '用户名' }),
getters: { name: (state) => state.name, },
actions: {
//同步actionupdataUser(data: any) {
this.name = data;
}
//异步actionasync updataUser2(data: any) {
const { data } = await api.login(account, pwd)
return data\
}
}
persist: {
// 开启数据缓存,必须开启
enabled: true,
//数据默认存在 sessionStorage 里,并且会以 store 的 id 作为 key。
strategies: [
{
//修改key值为my_user,选择开启。key: 'my_user',
//将存放位置由 sessionStorage 改为 localStorage,选择开启。
storage: localStorage,
//默认所有 state 都会进行缓存,你可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
paths: ['name']
}
]
}
})
useMainStore.use(useMainStore)
export default useMainStore
复制代码
7. 配置 css 预处理器 scss
安装scss
npm install -D sass-loader node-sass
复制代码
配置全局 scss 样式文件
在 src/assets 下新增 style 文件夹,用于存放全局样式文件,新建 index.scss,
html {
box-sizing: border-box;
}
复制代码
修改入口文件 mian.ts :
import { createApp } from'vue'importAppfrom'./App.vue'importElementPlusfrom'element-plus'import'element-plus/dist/index.css'import router from'./router/index'import { createPinia } from"pinia"import'./styles/index.scss'const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.use(createPinia())
app.mount('#app')
复制代码
8.统一请求封装
安装 axios
yarn add axios
复制代码
新增 utils 文件夹,utils 下新增 http.js 文件以及 errorCode.ts 文件和 common.ts 文件和loading.ts文件:
http.ts : 用于axios封装
import axios from'axios'import errorCode from'@/utils/errorCode'import { ElMessage, ElMessageBox } from'element-plus'import { getToken } from'@/utils/auth'import { tansParams } from"@/utils/common";
import { showLoading, closeLoading } from"@/utils/loading";
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'// 创建axios实例const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: process.env.VUE_APP_BASE_API,
// 超时timeout: 10000
})
//开启loadingshowLoading()
// request拦截器
service.interceptors.request.use(config => {
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (config.method === 'post' && config.params) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
//关闭loading,此处采用延时处理是合并loading请求效果,避免多次请求loading关闭又开启setTimeout(() => {
closeLoading()
}, 200)
// 未设置状态码则默认成功状态const code = res.data.code || 200;
// 获取错误信息const msg = res.data.msg || errorCode[code] || errorCode['default']
// 二进制数据则直接返回if(res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer'){
return res.data
}
// 判断状态码if(code === 200){
return res.data
}elseif(code === 401){
//跳转首页逻辑returnPromise.reject(msg)
}else{
ElMessage({
message: msg,
type: 'error'
})
returnPromise.reject(msg)
}
},
error => {
setTimeout(() => {
closeLoading()
}, 200)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
}
elseif (message.includes("timeout")) {
message = "系统接口请求超时";
}
elseif (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({
message: message,
type: 'error',
duration: 5 * 1000
})
returnPromise.reject(error)
}
)
exportdefault service
复制代码
errorCode.ts : 用于定义返回错误code
exportdefault {
'400': '请求头错误',
'401': '认证失败,无法访问系统资源, 请重新登录',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'500': '服务器端出错,
'503': '服务不可用',
'default': '系统未知错误,请反馈给管理员'
}
复制代码
common.ts : 用于定义一些共通方法
/**
* 参数处理
*/exportfunctiontansParams(params) {
let result = ''for (const propName ofObject.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key ofObject.keys(value)) {
if (value[key] !== null && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result
}
复制代码
loading.ts : 用于定义接口loading,不用每个页面单独去v-loading
/**
* 全局loading效果:合并多次loading请求,避免重复请求
* 当调用一次showLoading,则次数+1;当次数为0时,则显示loading
* 当调用一次closeLoading,则次数-1; 当次数为0时,则结束loading
*/
import { ElLoading } from 'element-plus';
// 定义一个请求次数的变量,用来记录当前页面总共请求的次数
let loadingRequestCount = 0;
// 初始化loading
let loadingInstance;
// 编写一个显示loading的函数 并且记录请求次数 ++
export showLoading = (target) => {
if (loadingRequestCount === 0) {
loadingInstance = ElLoading.service({
lock: true,
text: '加载中……',
background: 'rgba(0, 0, 0, 0.7)'
});
}
loadingRequestCount++
}
// 编写一个隐藏loading的函数,并且记录请求次数 --
export closeLoading = () => {
if (loadingRequestCount <= 0) return
loadingRequestCount--
if (loadingRequestCount === 0) {
loadingInstance.close();
}
}