vite + vue3+ ts +eslint + lint-staged

627 阅读17分钟

github.com/hejiyun/vit…

start

  1. 修改 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/, '')
        }
      } 
    } 
  });
  1. 修改 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

  1. 安装 'vite-vue3-ts'
    npm init vite vite-vue3-ts npm i npm run vite-vue3-ts

  2. 安装依赖

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
  1. 根目录下创建 .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、continuebreak 语句之后出现不可达代码 
      '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',
  },
}
  1. 根目录下创建 .eslintignore

# eslint 忽略检查 (根据项目需要自行添加)
node_modules 
dist 
public
pro // 打包文件也需要写入忽略文件中

prettier

  1. 安装依赖 npm init prettier --save--dev npm i eslint-config-prettier --dev

  2. 根目录下创建 .prettierrc.js


module.exports = { 
    tabWidth: 2,
    jsxSingleQuote: true,
    jsxBracketSameLine: true, 
    printWidth: 100, 
    singleQuote: true, 
    semi: false, 
    overrides: [ 
        { 
            files: '*.json',
            options: { 
                printWidth: 200, 
            }, 
        }, 
    ],
    arrowParens: 'always', 
}
  1. 根目录下创建.prettierignore
# 忽略格式化文件 (根据项目需要自行添加) 
node_modules
dist
public 
pro
  1. 修改启动命令 package.json

// package.json 
"script": { 
    "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
    "prettier": "prettier --write ." 
}

lint-staged + husky

  1. install mrm npm i mrm -D --registry=registry.npm.taobao.org
npm i mrm -D --registry=https://registry.npm.taobao.org
  1. 创建 git house

git init npx husky-init
  1. 安装 lint-staged

npx mrm lint-staged tips: it must requier your eslint set in devDependencies 
"devDependencies": { ... "eslint": "^8.33.0", ... },
  1. 修改 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" ]
     } 
}
  1. 修改 .husky-pre-commit
#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" 
npm test (need remove ---- esle error : missscript: test) 
npx lint-staged
  1. 修改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>
  1. 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

  1. 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
  1. 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'], 
        }, 
    ]
} 
  1. 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 限制长度 
 }  
  1. 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

  1. install pinia
    npm i pinia --save

  2. 在src文件夹下创建store文件夹,再创建index.ts 作为pinia仓库


// store --> index.ts
import { createPinia } from "pinia";
const store = createPinia() 
export default store
  1. 在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')
  1. 在store下创建user.ts

import { defineStore } from "pinia"; 
export const useUserStore = defineStore({ 
    id: 'user', 
    state: function (){ return { name: '张三' } },
    actions:{ updateName(name:string) { this.name = name } } 
})
  1. 测试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>
  1. 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>
  1. 修改state , 使用actions中方法修改值
<script lang="ts" setup> 
    import { useUserStore } from '@/store/user' 
    const userStore = useUserStore() 
    userStore.updateName('李四') 
</script>
  1. getters, 获取state中响应式复杂运算后的属性值

export const useUserStore = defineStore({ 
    id: 'user', 
    state: () => { return { name: '张三' } }, 
    getters: { fullName: (state) => { return state.name + '丰' } } 
}) 
// vue userStore.fullName // 张三丰
  1. 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 
        } 
    }, 
})
  1. 同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 
    } 
},
  1. 不同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
    } 
},
  1. 数据持久化 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
  1. 然后在需要持久化的store中, 开启Persist
export const useUserStore = defineStore({ 
    id: 'user', 
    state: () => { return { name: '张三' } },
    // 开启数据缓存 
    persist: { enabled: true } 
})
  1. persist自定义key值及存储位置
persist: { 
    enabled: true,
    strategies: [ { key: 'my_user', storage: localStorage, } ] 
}
  1. persist持久化部分值其他值不持久化
persist: { 
    enabled: true,
    strategies: [ { storage: localStorage, paths: ['name', 'age'] // paths,定义需要持久化的属性名称 } ] 
}

vue-router4

  1. install vue-router4
npm i vue-router --save
  1. 新建 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;
  1. 在 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')
  1. 修改 App.vue
<template> 
    <RouterView/> 
</template>

css

  1. css 新特性 variable , 新建文件 src/styles/index.css
:root { --main-bg-color: pink; } ​
body { background-color: var(--main-bg-color); }
  1. 导入 scss
npm add -D sass
  1. 样式文件动态变量的全局导入, 作用是可以在任意位置使用变量样式, 不需要一个个单独引用
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

  1. install axios
npm i axios
  1. 新建 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;
  1. 使用
<script lang="ts"> 
    import request from '@/utils/axios';
    const requestRes = async () => { 
        let result = await request({ url: '/api/xxx', method: 'get' }); 
    } 
</script>
  1. 封装请求参数和响应数据的所有 api (可选项) , 1.新建 src/api/index.ts
import * as login from './module/login'; 
import * as index from './module/index';
export default Object.assign({}, login, index);
  1. 新建 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 }
    }); 
};
  1. 使用
<script lang="ts"> 
    import API from '@/api';
    const requestRes = async () => {
        let result = await API.login('zhangsan', '123456');
    } 
</script>
  1. 为 axios添加重复请求过滤, 类似于数组去重, 每次进入请求都进行记录, 只要出现重复数据,则中断旧数据并清除出去, 保留最新数据
  2. 先处理去重所需函数方法
// 基本原理: 类似于数组的去重, 在不断添加的数组数据中,只要出现重复相同的数据, 则中断旧数据的处理并清除出去
// 保留最新数据
// 具体原理: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
}
  1. 然后在拦截器中添加去重逻辑, 每次请求前, 如果有相同的, 先要取消一个,并且在请求成功后需要执行移除操作,最终代码
// 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

  1. 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: 打包具体配置, 可以指定打包过程中的文件处理及最终打包文件生成位置,配置中有特别注意的地方需要关注理解
 */

  1. 打包环境获取 , 引入添加 loadEnv
// 关于打包环境配置, 首先引入 loadEnv环境变量, 它可以手动读取对应配置文件参数.
import { defineConfig, loadEnv } from 'vite'
  1. 根目录下添加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"
  1. 使用箭头函数修改一下输出的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 { //这里需要输入具体配置内容 } 
})
  1. 根据上述的属性介绍, 逐个添加属性及对应的值, 最终添加的后文件如下
// 关于打包环境配置,  首先引入 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()],
  }
})
  1. 在添加完打包配置build后, 由于使用了环境变量区分的关系, 所以, 需要在package.json中修改build指令的执行方式
"scripts": { 
    "build": "vite build --mode development",
    "build:pro": "vite build --mode production", 
},
  1. 执行打包命令, 测试打包结果对应的目标文件是否正确
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')
  1. 使用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" ], 
}
  1. 还需要在tsconfig.json中添加types
"compilerOptions": { 
    ... 
    "types": ["element-plus/global"], 
    ... 
}