start
- 修改 vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import * as path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
//设置别名
alias: { '@': path.resolve(__dirname, 'src') } },
plugins: [vue()],
server: {
port: 8080, //启动端口
hmr: { host: '127.0.0.1', port: 8080 }, // 设置 https 代理
proxy: {
'/api': {
target: 'your https address',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, '')
}
}
}
});
- 修改 tsconfig.json 添加 属性 'baseUrl' 、 'paths'
{
"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": "./",
"paths":{ "@": ["src"], "@/*": ["src/*"], } },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
}
eslint +prettier + lint-staged +custom-commitint
eslint
-
安装 'vite-vue3-ts'
npm init vite vite-vue3-ts npm i npm run vite-vue3-ts
-
安装依赖
npm i eslint --save--dev // eslint安装 tips: eslint must be install in Devdependdence, else lint-staged will be error by cannot found.
npm i eslint-plugin-vue --dev //eslint插件
npm i @typescript-eslint/eslint-plugin --dev // ts格式
npm i eslint-plugin-prettier --dev // 格式插件
npm i @typescript-eslint/parser --dev
- 根目录下创建 .eslintrc.js
module.exports = {
root: true,
env: { browser: true, node: true, es2021: true, },
parser: 'vue-eslint-parser',
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended', // eslint-config-prettier 的缩写 'prettier',
],
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaFeatures: { jsx: true, },
}, // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写
plugins: ['vue', '@typescript-eslint', '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',
// 禁止出现; semi: ['warn', 'never'],
// 强制在块之前使用一致的空格
'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',
'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',
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
}
- 根目录下创建 .eslintignore
# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist
public
pro // 打包文件也需要写入忽略文件中
prettier
-
安装依赖
npm init prettier --save--dev npm i eslint-config-prettier --dev
-
根目录下创建 .prettierrc.js
module.exports = {
tabWidth: 2,
jsxSingleQuote: true,
jsxBracketSameLine: true,
printWidth: 100,
singleQuote: true,
semi: false,
overrides: [
{
files: '*.json',
options: {
printWidth: 200,
},
},
],
arrowParens: 'always',
}
- 根目录下创建.prettierignore
# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist
public
pro
- 修改启动命令 package.json
// package.json
"script": {
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
"prettier": "prettier --write ."
}
lint-staged + husky
- install mrm npm i mrm -D --registry=registry.npm.taobao.org
npm i mrm -D --registry=https://registry.npm.taobao.org
- 创建 git house
git init npx husky-init
- 安装 lint-staged
npx mrm lint-staged tips: it must requier your eslint set in devDependencies
"devDependencies": { ... "eslint": "^8.33.0", ... },
- 修改 husky and lint , 修改后 package.json
{
"name": "vite_vue3_ts",
"private": true,
"version": "0.0.0",
// "type": "module",
//remove type of module, so lint can be used error: require() of ES Module /Users/HJY/Desktop/test/vite_vue3_ts/.eslintrc.js from // /Users/HJY/Desktop/test/vite_vue3_ts/node_modules/@eslint/eslintrc/dist/eslintrc.cjs not supported.
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
"prettier": "prettier --write .",
"prepare": "husky install"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.9.0",
"prettier": "^2.8.3",
"vue": "^3.2.41"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"eslint": "^8.33.0",
"husky": "^8.0.0",
"lint-staged": "^13.1.0",
"mrm": "^4.1.13",
"typescript": "^4.6.4",
"vite": "^3.2.3",
"vue-tsc": "^1.0.9"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [ "npm run lint", // dont't forgot ' run, "prettier --write" ]
}
}
- 修改 .husky-pre-commit
#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh"
npm test (need remove ---- esle error : missscript: test)
npx lint-staged
- 修改app.vue
// app.vue
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import {ref} from 'vue'
const count = ref(0)
console.log(`${count.value}`) console.log(1 == 2)
console.log(1 != 3) console.log('test one')
</script>
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo { height: 6em; padding: 1.5em; will-change: filter; }
.logo:hover { filter: drop-shadow(0 0 2em #646cffaa); }
.logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); }
</style>
- then , git add . && git commit -m 'test', obviously, it used
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import { ref } from 'vue'
const count = ref(0)
console.log(`${count.value}`)
console.log(1 === 2)
console.log(1 !== 3)
console.log('test one')
</script>
commitlint/custom
- use commitlint to standard team code commit
npm i commitlint @commitlint/config-conventional -D
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' //解决subject报错问题
npm install --save-dev @commitlint/prompt-cli
npm install commitizen cz-customizable cz-conventional-changelog -D
- create commitlint.config.js in root to set commit-msg
// commitlint.config.js
module.exports = {
extends: [
'@commitlint/config-conventional', // scoped packages are not prefixed ],
rules: {
'type-enum': [ 2, 'always', ['feat', 'fix', 'fix', 'docs', 'style', 'refactor', 'perf', 'ci', 'chore'], ],
'type-case': [2, 'always', ['lower-case', 'upper-case']], 'scope-case': [0, 'never'],
'subject-case': [0, 'never'],
'scope-empty': [2, 'never'],
},
]
}
- create .cz.configrc.js in root to set commit-msg
'use strict'
module.exports = {
types: [
{ value: 'feat', name: '🚀 新增功能' },
{ value: 'fix', name: '🐛 修复 bug' },
{ value: 'docs', name: '📝 文档变更' },
{ value: 'style', name: '💄 代码格式(不影响功能,例如空格、分号等格式修正)', },
{ value: 'refactor', name: '💩 代码重构(不包括 bug 修复、功能新增)/重写屎一样的代码', },
{ value: 'perf', name: '⚡️ 性能优化' },
{ value: 'ci', name: '💚 对配置文件和脚本的更改' },
{ value: 'chore', name: '🧹 其他修改, 比如构建流程, 依赖管理、版本好修正.' },
],
// scope 类型(定义之后,可通过上下键选择)
scopes: [
['components', '组件相关'],
['hooks', 'hook 相关'],
['utils', 'utils 相关'],
['element-ui', '对 element-ui 的调整'],
['styles', '样式相关'],
['deps', '项目依赖'],
['auth', '对 auth 修改'],
['other', '其他修改'],
// 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true
['custom', '以上都不是?我要自定义'],
].map(([value, description]) => {
return { value, name: `${value.padEnd(5)} (${description})`, }
}),
// 交互提示信息
messages: {
type: '确保本次提交遵循:前端代码规范!\n选择你要提交的类型:',
scope: '\n选择一个 scope(可选):',
// 选择 scope: custom 时会出下面的提示
customScope: '请输入自定义的 scope:',
subject: '填写简短精炼的变更描述:\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n',
breaking: '列举非兼容性重大的变更(可选):\n',
footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n',
confirmCommit: '确认提交?',
},
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix'],
subjectLimit: 100, // subject 限制长度
}
- add instroction in package to use commitlint
// package.json
{
"name": "vite-vue3-ts",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
"prettier": "prettier --write .",
"prepare": "husky install",
"commit": "git add . && npx cz"
},
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
},
"cz-customizable": {
"config": ".cz-configrc.js"
}
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"eslint": "^8.33.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.9.0",
"prettier": "^2.8.3",
"vue": "^3.2.41"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.4.2",
"@commitlint/prompt-cli": "^17.4.2",
"@vitejs/plugin-vue": "^3.2.0",
"commitizen": "^4.3.0",
"commitlint": "^17.4.2",
"cz-customizable": "^7.0.0",
"eslint": "^8.33.0",
"husky": "^8.0.0",
"lint-staged": "^13.1.0",
"mrm": "^4.1.13",
"typescript": "^4.6.4",
"vite": "^3.2.3",
"vue-tsc": "^1.0.9"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [ "npm run lint", "prettier --write" ]
}
}
pinia
-
install pinia
npm i pinia --save
-
在src文件夹下创建store文件夹,再创建index.ts 作为pinia仓库
// store --> index.ts
import { createPinia } from "pinia";
const store = createPinia()
export default store
- 在main中引入
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
- 在store下创建user.ts
import { defineStore } from "pinia";
export const useUserStore = defineStore({
id: 'user',
state: function (){ return { name: '张三' } },
actions:{ updateName(name:string) { this.name = name } }
})
- 测试pinia ,在 components下创建usePinia.vue
<template>
<div>{{ userStore.name }}</div>
</template>
<script lang="ts" setup>
import { useUserStore } from '@/store/user'
const userStore = useUserStore() // 获取 userStore.updateName('李四')
// 修改
</script>
- store值解构
<script lang="ts" setup>
import { useUserStore } from '@/store/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore() // 获取
userStore.updateName('李四') // 修改
// const { name } = userStore //state可以使用解构获取值, 但是直接解构会让值失去响应式,
const { name } = storeToRefs(userStore) // 而使用 pinia的 storeToRefs 可以在解构的同时,保留其响应式特点
</script>
- 修改state , 使用actions中方法修改值
<script lang="ts" setup>
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
userStore.updateName('李四')
</script>
- getters, 获取state中响应式复杂运算后的属性值
export const useUserStore = defineStore({
id: 'user',
state: () => { return { name: '张三' } },
getters: { fullName: (state) => { return state.name + '丰' } }
})
// vue userStore.fullName // 张三丰
- actions 异步数据处理
export const useUserStore = defineStore({
id: 'user',
actions: { updateName(name: string) { this.name = name },
async login(account:number, pwd:string) {
const { data } = await api.login(account, pwd)
return data
}
},
})
- 同store下actions方法调用, 直接this.调用
actions: {
updateName(name: string) { this.name = name },
// 使用 async await 获取异步请求后数据
async login(account:number, pwd:string) {
this.updateName('张三疯') // 同store的action方法可以直接this.调用
const { data } = await api.login(account, pwd)
return data
}
},
- 不同store下actions方法调用。 先引入, 然后调用
// store --> frends.ts
import { defineStore } from 'pinia'
export const useFrendsStore = defineStore({
id: 'frends',
actions: { setData(data:any) { console.log(data) } }
})
// store --> user.ts
import { useFrendsStore } from './frends' //先引入
actions: { updateName(name: string) { this.name = name },
// 使用 async await 获取异步请求后数据
async login(account:number, pwd:string) {
this.updateName('张三疯') // 同store的action方法可以直接this.调用
const { data } = await api.login(account, pwd)
const appStore = useFrendsStore() // 引入完成正常使用 appStore.setData(data)
// 调用 app store 里的 action 方法
return data
}
},
- 数据持久化 npm i pinia-plugin-persist --save , 安装完成后, 先在index.ts中添加piniaPluginPersist
// src/store/index.ts
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia()
store.use(piniaPluginPersist)
export default store
- 然后在需要持久化的store中, 开启Persist
export const useUserStore = defineStore({
id: 'user',
state: () => { return { name: '张三' } },
// 开启数据缓存
persist: { enabled: true }
})
- persist自定义key值及存储位置
persist: {
enabled: true,
strategies: [ { key: 'my_user', storage: localStorage, } ]
}
- persist持久化部分值其他值不持久化
persist: {
enabled: true,
strategies: [ { storage: localStorage, paths: ['name', 'age'] // paths,定义需要持久化的属性名称 } ]
}
vue-router4
- install vue-router4
npm i vue-router --save
- 新建 src/router 目录并在其下面创建 index.ts,导出 router
// src --> router --> index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
const routes: Array<RouteRecordRaw> = [
{
path: '/user',
name: 'User',
meta: { title: '用户', keepAlive: true, requireAuth: false },
component: () => import('@/components/usePinia.vue')
},
{
path: '/',
name: 'Index',
meta: { title: '首页', keepAlive: true, requireAuth: true },
component: () => import('@/components/HelloWorld.vue')
}
]
const router = createRouter({ history: createWebHistory(), routes });
export default router;
- 在 main.ts 中引入并使用
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import store from './store'
import router from '@/router';
createApp(App).use(store).use(router).mount('#app')
- 修改 App.vue
<template>
<RouterView/>
</template>
css
- css 新特性 variable , 新建文件 src/styles/index.css
:root { --main-bg-color: pink; }
body { background-color: var(--main-bg-color); }
- 导入 scss
npm add -D sass
- 样式文件动态变量的全局导入, 作用是可以在任意位置使用变量样式, 不需要一个个单独引用
npm i style-resources-loader
npm i vue-cli-plugin-style-resources-loader // 然后在 vite.config.js中导入第三方插件配置
//vite.config.js
export default defineConfig(({mode}) => {
...
return{
....
pluginOptions: {
'style-resources-loader': {
preProcessor: 'sass', // 类型
patterns: [path.resolve(__dirname, './src/assets/sass/global.scss')]
// 这里添加变量样式存储的目标文件
}
},
}
}
axios
- install axios
npm i axios
- 新建 src/utils/axios.ts
import Axios from "axios"
const BASE_URL = ''; //请求接口url 如果不配置 则默认访问链接地址
const TIME_OUT = 6000; // 接口超时时间
const instance = Axios.create({ baseURL:BASE_URL, timeout:TIME_OUT }) // 可以添加一个axios的post全局配置
instance.defaults.headers.post = {
"Content-Type":'application/x-www-form-urlencoded',
"USER-PFID": 0,
"USER-TOKEN": 0,
locale: localStorage.getItem("SITE_LANG") || "zh-tw",
"APP-NAME": 0 || "art",
"APP-VERSION": 0 || "1.0.6"
}
// 添加请求拦截器
instance.interceptors.request.use(
(config) => {
// 可以在此处添加一些共有的请求拦截
return config
},(error) => {
return Promise.reject(error);
}
)
// 添加响应拦截器
instance.interceptors.response.use(
(response)=>{
const res = response.data;
// 在此处添加一些响应拦截
return response
},(error)=>{
return Promise.reject(error);
}
)
export default instance;
- 使用
<script lang="ts">
import request from '@/utils/axios';
const requestRes = async () => {
let result = await request({ url: '/api/xxx', method: 'get' });
}
</script>
- 封装请求参数和响应数据的所有 api (可选项) , 1.新建 src/api/index.ts
import * as login from './module/login';
import * as index from './module/index';
export default Object.assign({}, login, index);
- 新建 src/api/module/login.ts 和 src/api/module/index.ts
import request from '@/utils/axios';
/** * 登录 */
interface IResponseType<P = {}> {
code?: number;
status: number;
msg: string;
data: P;
}
interface ILogin {
token: string;
expires: number;
}
export const login = (username: string, password: string) => {
return request<IResponseType<ILogin>>({
url: '/api/auth/login',
method: 'post',
data: { username, password }
});
};
- 使用
<script lang="ts">
import API from '@/api';
const requestRes = async () => {
let result = await API.login('zhangsan', '123456');
}
</script>
- 为 axios添加重复请求过滤, 类似于数组去重, 每次进入请求都进行记录, 只要出现重复数据,则中断旧数据并清除出去, 保留最新数据
- 先处理去重所需函数方法
// 基本原理: 类似于数组的去重, 在不断添加的数组数据中,只要出现重复相同的数据, 则中断旧数据的处理并清除出去
// 保留最新数据
// 具体原理:https://axiu.me/coding/axios-use-canceltoken-to-cancel-request/
//声明一个数组用于存储每个ajax请求的取消函数和ajax标识
const rqList: any = []
const cancelToken = Axios.CancelToken
const removeRepeatUrl = (ever: any) => {
for (const rq in rqList) {
// 判断是否存在重复请求
if (
rqList[rq].config &&
rqList[rq].config.url === ever.url &&
rqList[rq].config.method === ever.method
) {
if (isObjectValueEqual(rqList[rq].config, ever)) {
//当当前请求在数组中存在时,取消操作,并将其从数组中移除
rqList[rq].cancle()
}
rqList.splice(rq, 1)
}
}
}
const isObjectValueEqual = (a: any, b: any) => {
console.log(a, b)
// 判断两个对象是否指向同一内存,指向同一内存返回true,同时比较null和undefined情况
if (a == b) {
return true
}
if (a == null || a == undefined || b == null || b == undefined) {
return false
}
// 获取两个对象键值数组
const aProps = Object.getOwnPropertyNames(a)
const bProps = Object.getOwnPropertyNames(b)
// 判断两个对象键值数组长度是否一致,不一致返回false
if (aProps.length !== bProps.length) {
return false
}
// 遍历对象的键值
for (const prop in a) {
// 判断a的键值,在b中是否存在,不存在,返回false
if (b.hasOwnProperty(prop)) {
// 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false
if (typeof a[prop] === 'object') {
if (!isObjectValueEqual(a[prop], b[prop])) {
return false
}
} else if (a[prop] !== b[prop]) {
return false
}
} else {
return false
}
}
return true
}
- 然后在拦截器中添加去重逻辑, 每次请求前, 如果有相同的, 先要取消一个,并且在请求成功后需要执行移除操作,最终代码
// axios.ts
import Axios from 'axios'
const BASE_URL = '' //请求接口url 如果不配置 则默认访问链接地址
const TIME_OUT = 30000 // 接口超时时间
const instance = Axios.create({
baseURL: BASE_URL,
timeout: TIME_OUT,
})
// 可以添加一个axios的post全局配置
instance.defaults.headers.post = {
'Content-Type': 'application/x-www-form-urlencoded',
'USER-PFID': 0,
'USER-TOKEN': 0,
locale: localStorage.getItem('SITE_LANG') || 'zh-tw',
'APP-NAME': 0 || 'art',
'APP-VERSION': 0 || '1.0.6',
}
// 基本原理: 类似于数组的去重, 在不断添加的数组数据中,只要出现重复相同的数据, 则中断旧数据的处理并清除出去
// 保留最新数据
// 具体原理:https://axiu.me/coding/axios-use-canceltoken-to-cancel-request/
//声明一个数组用于存储每个ajax请求的取消函数和ajax标识
const rqList: any = []
const cancelToken = Axios.CancelToken
const removeRepeatUrl = (ever: any) => {
for (const rq in rqList) {
// 判断是否存在重复请求
if (
rqList[rq].config &&
rqList[rq].config.url === ever.url &&
rqList[rq].config.method === ever.method
) {
if (isObjectValueEqual(rqList[rq].config, ever)) {
//当当前请求在数组中存在时,取消操作,并将其从数组中移除
rqList[rq].cancle()
}
rqList.splice(rq, 1)
}
}
}
// 添加请求拦截器
instance.interceptors.request.use(
(config) => {
console.log('config', config)
//在一个ajax发送前执行一下取消操作
removeRepeatUrl({
method: config.method,
url: config.url,
params: config.params,
data: config.data,
})
// 创建cancleToken和cancle取消请求方法, 每个请求都唯一
config.cancelToken = new cancelToken((c: any) => {
// 自定义唯一标识
rqList.push({
config: {
method: config.method,
url: config.url,
params: config.params,
data: config.data,
},
cancle: c, //
})
})
// 可以在此处添加一些共有的请求拦截
return config
},
(error) => {
return Promise.reject(error)
}
)
// 添加响应拦截器
instance.interceptors.response.use(
(response) => {
console.log('响应', response)
removeRepeatUrl({
method: response.config.method,
url: response.config.url,
params: response.config.params,
data: response.config.data,
}) //在一个ajax响应后再执行一下取消操作,把已经完成的请求从rqList中移除
const data = response.data
return data
},
(error) => {
return Promise.reject(error)
}
)
export default instance
const isObjectValueEqual = (a: any, b: any) => {
console.log(a, b)
// 判断两个对象是否指向同一内存,指向同一内存返回true,同时比较null和undefined情况
if (a == b) {
return true
}
if (a == null || a == undefined || b == null || b == undefined) {
return false
}
// 获取两个对象键值数组
const aProps = Object.getOwnPropertyNames(a)
const bProps = Object.getOwnPropertyNames(b)
// 判断两个对象键值数组长度是否一致,不一致返回false
if (aProps.length !== bProps.length) {
return false
}
// 遍历对象的键值
for (const prop in a) {
// 判断a的键值,在b中是否存在,不存在,返回false
if (b.hasOwnProperty(prop)) {
// 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false
if (typeof a[prop] === 'object') {
if (!isObjectValueEqual(a[prop], b[prop])) {
return false
}
} else if (a[prop] !== b[prop]) {
return false
}
} else {
return false
}
}
return true
}
项目配置 vite-config.js
- config文件属性介绍
/** 首先, 在进行项目底层配置时, 需要了解config文件配置中,常见的,也就是一般来讲需要去配置的属性都有哪一些, 分别有什么作用
*
* 1. publicPath: 加载的静态资源访问路径, 也就是打包之后, 在哪里去找这些静态资源. 比如 设置./, 那么打包之后,就会去./目录下找静态资源
*
* 2. indexPath: 相对于打包路径index.html的路径
*
* 3. outputDir: 输出文件的目录, 也就是打包后输出文件名称
*
* 4. assetsDir: 相对于outputDir的静态资源(js,css,img,fonts)目录
*
* 4. lintOnSave: eslint-loader是否在保存代码时检查, 这个可以设置也可以不设置.与代码规范有关
*
* 5.runtimeCompiler/runtime-only: 构建vue的版本选择, 两者的区别在于, 前者体积略大,性能略差, 但是可以选用template或render编写,
* 后者不包含编辑器,在渲染页面时能节省两步操作,性能略好, 但是只能使用render进行编写,灵活度不高.
*
* 6. productionSourceMap: 生产环境时是否开启sourceMap, 什么事sourceMap?可以理解为资源地图, 它可以在控制台准确显示输出语句位置.
* 对查错时很有帮助,因为打包之后代码都是压缩加密的, 如果运行时报错,输出信息时很难准确知道在哪里的.
* 当然缺点在于在打包时, 会对js文件生成对应的.map文件. 打包总体积增大.
*
* 7. pluginOptions: 主要用于修改第三方插件的配置
*
* 8. chainWebpack/configureWebpack: 两者的作用相同, 都是用于修改webpack默认的配置, 区别在于一个通过链式修改, 一个通过操作对象修改.
*
* 9. parallel: 构建时开启多进程处理 babel 编译, 单核无作用, 多核时自动开启
*
* 10. css: 项目中css类文件处理规则,
*
* 11. devServer: 配置开发环境服务器,实现请求代理,避免出现跨域
*
* 12. build: 打包具体配置, 可以指定打包过程中的文件处理及最终打包文件生成位置,配置中有特别注意的地方需要关注理解
*/
- 打包环境获取 , 引入添加 loadEnv
// 关于打包环境配置, 首先引入 loadEnv环境变量, 它可以手动读取对应配置文件参数.
import { defineConfig, loadEnv } from 'vite'
- 根目录下添加env文件夹用于存放环境配置文件, 并创建dev,test,pre, prod四个环境配置文件
// env ----> .env.development / .env.production / .env.pre / .env.test
NODE_ENV=development/test/pre/production // 分别对应不同文件
VITE_BASE_URL="http://127.0.0.1:3333"
- 使用箭头函数修改一下输出的defineConfig, 以便获取环境变量
// 关于打包环境配置, 首先引入 loadEnv环境变量, 它可以手动读取对应配置文件参数.
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig(({mode}) => {
const env = loadEnv(mode, './env') // 第二个参数为读取目标文件的位置 ——dirname 为当前项目根目录位置
console.log(env, 'zheli环境')
return { //这里需要输入具体配置内容 }
})
- 根据上述的属性介绍, 逐个添加属性及对应的值, 最终添加的后文件如下
// 关于打包环境配置, 首先引入 loadEnv环境变量, 它可以手动读取对应配置文件参数.
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, './env')
console.log(env, 'zheli环境')
return {
publicPath: './',
outputDir: 'dist',
assetsDir: './static',
indexPath: './index.html',
lintOnSave: true,
// runtimeCompiler: true,
// transpileDependencies: [],// 默认babel-loader忽略mode_modules,这里可增加例外的依赖包名
productionSourceMap: false,
resolve: {
alias: {
// 配置文件引用时, 快捷路径. 当前设置为当使用@引用时, 默认从src文件夹开始计算层级.
'@': path.resolve(__dirname, 'src'),
},
},
// 使用configureWebpack来配置生产环境文件压缩率(当然, 这个是举例使用方法.)
configureWebpack: (config: any) => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
config.mode = 'production'
config.performance = {
//打包文件大小配置
maxEntrypointSize: 10000000,
maxAssetSize: 30000000,
}
}
},
parallel: require('os').cpus().length > 1, // 构建时开启多进程处理 babel 编译
css: {
// modules: true, // 是否开启支持‘foo.module.css’样式
// extract: true, // 是否使用css分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用<style>方式内联至html文件中
// 开启 CSS source maps?
// sourceMap: false,
// css预设器配置项
loaderOptions: {
sass: {
data: '', //`@import "@/assets/scss/mixin.scss";`
},
css: {
// options here will be passed to css-loader
},
postcss: {
// options here will be passed to postcss-loader
},
},
// 启用 CSS modules for all css / pre-processor files.
modules: false,
},
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [path.resolve(__dirname, './src/assets/less/global.less')],
},
},
server: {
port: 8080, //启动端口
hmr: {
host: '127.0.0.1',
port: 8080,
},
// 设置 https 代理
proxy: {
'/api': {
target: 'your https address',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, ''),
},
},
},
build: {
// 通过mode来区别打包时输出的位置即可.
outDir: mode == 'development' ? 'dist' : 'prod', //指定输出路径
rollupOptions: {
output: {
//静态资源分类打包
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: function (AssetInfo) {
// 这里详细的说下, 这些属性可以使用方法去进行更详细的个人操作, 比如更细致的分类等等,自定义设置.
return AssetInfo.name.split('.')[1] !== 'css'
? 'static/img/[name]-[hash].[ext]'
: 'static/[ext]/[name]-[hash].[ext]'
},
manualChunks(id) {
//静态资源分拆打包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString()
}
},
},
},
},
plugins: [vue()],
}
})
- 在添加完打包配置build后, 由于使用了环境变量区分的关系, 所以, 需要在package.json中修改build指令的执行方式
"scripts": {
"build": "vite build --mode development",
"build:pro": "vite build --mode production",
},
- 执行打包命令, 测试打包结果对应的目标文件是否正确
npm run build npm run build:pro
开发-Element plus
npm install element-plus --save
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from '@/router'
const app = createApp(App) app.use(store) app.use(router) app.use(ElementPlus) app.mount('#app')
- 使用volar进行vue拆分开发 , 图标默认已隐藏, 可以在设置里打开 volar: icon: split editor 勾选开启
// 在 vscode 用户设置中 settings.json
{
"window.zoomLevel": 2,
"cssrem.addMark": true,
"cssrem.rootFontSize": 37.5,
"workbench.colorTheme": "Default Light+",
"volar.icon.splitEditors": true,
"volar.splitEditors.layout.left": [ "template", "script", "scriptSetup", ],
"volar.splitEditors.layout.right": [ "styles", "customBlocks" ],
}
- 还需要在tsconfig.json中添加types
"compilerOptions": {
...
"types": ["element-plus/global"],
...
}