环境 node: v16.15.1 npm: 8.12.1

本文项目地址:https://gitee.com/zhengqingya/small-tools 文档更新地址:https://gitee.com/zhengqingya/java-developer-document

技术栈
  1. Vue3
  2. Vite2
  3. TypeScript
  4. Vue Router
  5. Element Plus
  6. Pinia
一、项目初始化

创建项目

npm init vite@latest small-tools-web -- --template vue-ts

前端vue 怎么对接后台(前端项目搭建Vue3Vite2)(1)

在这里插入图片描述

二、编码规范配置

ESLint:编码规范检查 Prettier:代码格式化工具

1、安装

# eslint => ESLint的核心代码 # @typescript-eslint/parser => ESLint的解析器,用于解析ts,从而检查和规范ts代码 # @typescript-eslint/eslint-plugin => 一个ESLint插件,包含了各类定义好的检测ts代码的规范 # eslint-plugin-vue => vue3的代码校验 # prettier => prettier插件的核心代码 # eslint-config-prettier => 解决ESLint中的样式规范和prettier中样式规范的冲突,以prettier的样式规范为准,使ESLint中的样式规范自动失效 # eslint-plugin-prettier => 将prettier作为ESLint规范来使用 cnpm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue prettier eslint-config-prettier eslint-plugin-prettier

2、配置.eslintrc.js配置文件

https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-file-formats https://eslint.vuejs.org/rules

module.exports = { parser: '@typescript-eslint/parser', //定义ESLint的解析器 extends: [ 'prettier/@typescript-eslint', // 使@typescript-eslint中的样式规范失效,遵循prettier中的样式规范 'plugin:prettier/recommended' // 使用prettier中的样式规范,且如果使ESLint会检测prettier的格式问题,同样将格式问题以error的形式抛出 ], settings: { "react": { "pragma": "React", "version": "detect" } }, parserOptions: { "ecmaVersion": 2019, "sourceType": 'module', "ecmaFeatures": { jsx: true } }, env: { //指定代码的运行环境 browser: true, node: true, } };

.prettierrc.js配置文件

https://prettier.io/docs/en/configuration.html

module.exports = { printWidth: 120, semi: false, singleQuote: true, trailingComma: "all", bracketSpacing: false, jsxBracketSameLine: true, arrowParens: "avoid", insertPragma: true, tabWidth: 4, useTabs: false, };

.eslintignoreESLint忽略检查文件

node_modules *.md .vscode .idea dist /public .eslintrc.js src/assets

.prettierignorePrettier忽略格式化文件

/dist/* /node_modules/** **/*.svg /public/*

3、代码检查 & 自动修复 & 自动格式化

package.Json的scripts中添加

--ext: 配置要检查文件的后缀 eslint --ext .js --ext .ts --ext .vue src: 只检查src文件夹下的js/ts/vue文件

"scripts": { "lint": "eslint --ext .js --ext .ts --ext .vue src", "lint-fix": "eslint --ext .js --ext .ts --ext .vue src --fix", "prettier": "prettier --write ." }

# 代码检查 cnpm run lint # 自动修复 tips: 部分代码修复可能会影响功能,所以不会自动修复,会给出提示 cnpm run lint-fix # prettier 自动格式化 cnpm run prettier

4、统一代码风格

需安装插件EditorConfig for VS Code

.editorconfig

# https://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] insert_final_newline = false trim_trailing_whitespace = false

三、Vite配置1、配置路径别名

vite.config.ts

import * as path from 'path'; export default defineConfig({ resolve: { // 配置路径别名 alias: [ // @代替src { find: '@', replacement: path.resolve('./src') } ] }, })

解决:找不到模块“path”或其相应的类型声明

# 安装node声明文件 cnpm i @types/node --save-dev

2、环境变量

.env.dev

# 开发环境 NODE_ENV='dev' # 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。 # ts中通过`import.meta.env.VITE_APP_BASE_API`取值 VITE_APP_PORT = 5173 VITE_APP_BASE_API = '/dev-api'

3、反向代理解决跨域问题 & 配置项目运行端口

vite.config.ts

import { defineConfig, loadEnv } from 'vite' export default defineConfig(({ mode }) => { // 获取`.env`环境配置文件 const env = loadEnv(mode, process.cwd()); return { server: { host: 'localhost', port: Number(env.VITE_APP_PORT), // 运行时自动打开浏览器 // open: true, proxy: { [env.VITE_APP_BASE_API]: { target: 'http://localhost:1218', changeOrigin: true, rewrite: path => path.replace(new RegExp('^' env.VITE_APP_BASE_API), '') } } } } })

4、引入scss全局变量

vite.config.ts

export default defineConfig(({ mode }) => { return { // 引入scss全局变量 css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/app-theme.scss";` } } } } })

src/styles/app-theme.scss

$dark_main_color: #021b32;

四、TypeScript配置tsconfig.json

通过vite创建项目后的默认配置

{ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "moduleResolution": "Node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] }


解决:找不到模块“@/router”或其相应的类型声明。

tsconfig.json新增如下配置

"baseUrl": ".", "paths": { "@/*": [ "src/*" ] }

解决:template中使用$store报错

vite-env.d.ts

import { Store } from '@/store' declare module '@vue/runtime-core' { interface ComponentCustomProperties { $store: Store; } }

五、package.json

package.json

{ "name": "small-tools-web", "private": true, "version": "1.0.1", "type": "module", "scripts": { // 运行 "dev": "vite --mode dev", // 构建生成 dist 文件夹 "build:prod": "vue-tsc --noEmit && vite build --mode prod", // 在本地启动一个静态 Web 服务器,将 dist 文件夹运行在 http://localhost:8080 "preview": "vite preview --port 8080 --mode prod", // eslint检查 "lint": "eslint --ext .js --ext .ts --ext .vue src", // eslint自动修复 "lint-fix": "eslint --ext .js --ext .ts --ext .vue src --fix", // prettier自动格式化代码 "prettier": "prettier --write ." }, "dependencies": { "vue": "^3.2.37", "vue-router": "^4.1.2" }, "devDependencies": { "@types/node": "^18.0.5", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", "@vitejs/plugin-vue": "^3.0.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-vue": "^9.2.0", "prettier": "^2.7.1", "typescript": "^4.6.4", "vite": "^3.0.0", "vue-tsc": "^0.38.4" } }


# devDependencies: 里面的插件只用于开发环境,不用于生产环境 # dependencies: 需要发布到生产环境的 # 写入到 dependencies 对象 npm i module_name -S => npm install module_name --save # 写入到 devDependencies 对象 npm i module_name -D => npm install module_name --save-dev

六、Sass

安装

cnpm install sass --save-dev

七、Vue Router

https://router.vuejs.org/zh

1、安装

cnpm install vue-router@4

2、入门配置

src/router/index.ts

import {createRouter, createWebHashHistory} from 'vue-router'; // 静态路由 export const routes = [ { path: '/login', component: () => import('@/views/login/index.vue') }, { path: '/404', component: () => import('@/views/error-page/404.vue') }, ]; // 创建路由 const router = createRouter({ history: createWebHashHistory(), routes }); export default router;

src/main.ts

// ****** ↓↓↓ 路由 ↓↓↓ ****** import router from '@/router'; const app = createApp(App); app.use(router); app.mount('#app')

src/views/error-page/404.vue

<template> <h1>404</h1> </template>

src/App.vue

<template> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view/> </template>

访问http://ip:端口/#/404

八、Element Plus

https://element-plus.gitee.io/zh-CN

1、安装

cnpm install element-plus --save cnpm install @element-plus/icons-vue

2、配置

main.ts

// ****** ↓↓↓ element-plus ↓↓↓ ****** import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 注册所有图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } const app = createApp(App) app.use(ElementPlus) app.mount('#app')

3、Volar 支持

tsconfig.json中通过compilerOptions.types指定全局组件类型

{ "compilerOptions": { "types": [ "element-plus/global" ] } }

4、demo

<template> <el-button link>按钮</el-button> <el-icon class="is-loading"> <Loading/> </el-icon> <div> <el-button :icon="Search" circle/> <el-button type="primary" :icon="Edit" circle/> <el-button type="success" :icon="Check" circle/> <el-button type="info" :icon="Message" circle/> <el-button type="warning" :icon="Star" circle/> <el-button type="danger" :icon="Delete" circle/> </div> </template> <script lang="ts" setup> import { Check, Delete, Edit, Message, Search, Star, } from '@element-plus/icons-vue' </script>

九、自定义样式

main.ts

import '@/styles/index.scss';

src/styles/index.scss

@import './element-plus-theme'; body { background-color: #021b32; }

src/styles/element-plus-theme.scss

// ****** ↓↓↓ 覆盖 element-plus 的样式 ↓↓↓ ****** // 按钮 .el-button--text { // background-color: #8f6732 !important; margin-left: 3px; border: none !important; }

十、Pinia

https://pinia.vuejs.org

1、安装

cnpm install pinia --save

2、配置

main.ts

const app = createApp(App) // pinia import { createPinia } from 'pinia'; const pinia = createPinia() app.use(pinia) // store import useStore from "@/store"; app.config.globalProperties.$store = useStore(); app.mount('#app')

3、使用

src/store/index.ts

import useAppStore from './modules/app'; const useStore = () => ({ app: useAppStore() }); export default useStore;

src/store/modules/app.ts

import { AppState } from '@/types/store/app'; import { localStorage } from '@/utils/storage'; import { defineStore } from 'pinia'; const useAppStore = defineStore({ id: 'app', state: (): AppState => ({ name: localStorage.get('name') || 'Small Tools', }), actions: { setName(name: string) { this.name = name; localStorage.set('name', name); } } }); export default useAppStore;

src/utils/storage.ts

/** * window.localStorage => 浏览器永久存储,用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。 */ export const localStorage = { set(key: string, val: any) { window.localStorage.setItem(key, JSON.stringify(val)); }, get(key: string) { const json: any = window.localStorage.getItem(key); return JSON.parse(json); }, remove(key: string) { window.localStorage.removeItem(key); }, clear() { window.localStorage.clear(); }, }; /** * window.sessionStorage => 浏览器本地存储,数据保存在当前会话中,在关闭窗口或标签页之后将会删除这些数据。 */ export const sessionStorage = { set(key: string, val: any) { window.sessionStorage.setItem(key, JSON.stringify(val)); }, get(key: string) { const json: any = window.sessionStorage.getItem(key); return JSON.parse(json); }, remove(key: string) { window.sessionStorage.removeItem(key); }, clear() { window.sessionStorage.clear(); }, };

页面引用

<template> <p>store: {{ name }}</p> <p>store: {{ app.name }}</p> <p>store: {{ $store.app.name }}</p> <el-button @click="changeStore('666')">change store</el-button> </template> <script lang="ts" setup> import { storeToRefs } from 'pinia' import useStore from "@/store"; const { app } = useStore() // const name = ref(app.name) // 响应式 const { name: name } = storeToRefs(app) function changeStore(value: string) { app.setName(value) } </script>

十一、Axios和API封装

http://www.axios-js.com/zh-cn/docs

1、安装

cnpm install axios --save

2、axios工具封装

src/utils/request.ts

import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import { ElMessage, ElMessageBox } from 'element-plus'; import { localStorage } from '@/utils/storage'; import useStore from '@/store'; // 创建axios实例 const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, // 请求超时时间:50s timeout: 50000, headers: { 'Content-Type': 'application/json;charset=utf-8' }, }); // 请求拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { if (!config.headers) { throw new Error( `Expected 'config' and 'config.headers' not to be undefined` ); } const { user } = useStore(); if (user.token) { // 授权认证 config.headers.Authorization = user.token; } // 租户ID config.headers['TENANT_ID'] = '1' return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response: AxiosResponse) => { const res = response.data; const { code, msg } = res; if (code === 200) { return res; } else { // token过期 if (code === -1) { ElMessageBox.confirm("您的登录账号已失效,请重新登录", { confirmButtonText: "再次登录", cancelButtonText: "取消", type: "warning" }).then(() => { // 清除浏览器全部缓存 localStorage.clear(); // 跳转登录页 window.location.href = '/'; location.reload(); }); } else { ElMessage({ message: msg || '系统出错', type: 'error', duration: 5 * 1000 }); } return Promise.reject(new Error(msg || 'Error')); } }, (error) => { const { msg } = error.response.data; // 未认证 if (error.response.status === 401) { ElMessageBox.confirm("您的登录账号已失效,请重新登录", { confirmButtonText: "再次登录", cancelButtonText: "取消", type: "warning" }).then(() => { // 清除浏览器全部缓存 localStorage.clear(); // 跳转登录页 window.location.href = '/'; location.reload(); }); } else { ElMessage({ message: "网络异常,请稍后再试!", type: "error", duration: 5 * 1000 }); return Promise.reject(new Error(msg || 'Error')); } } ); // 导出实例 export default service;

3、api封装

src/api/index.ts

// 拿到所有api const modulesFiles = import.meta.globEager('./*/*.*'); const modules: any = {}; for (const key in modulesFiles) { const moduleName = key.replace(/(.*\/)*([^.] ).*/gi, '$2'); const value: any = modulesFiles[key]; if (value.default) { // 兼容js modules[moduleName] = value.default; } else { // 兼容ts modules[moduleName] = value; } } // console.log(666, modules); export default modules;

main.ts

const app = createApp(App); // 配置全局api import api from '@/api' app.config.globalProperties.$api = api; app.mount('#app')

4、api调用demo

src/api/system/sys_login.ts

import { Captcha } from '@/types/api/system/login'; import request from '@/utils/request'; import { AxiosPromise } from 'axios'; // 获取验证码 export function getCaptcha(): AxiosPromise<Captcha> { return request({ url: '/captcha?t=' new Date().getTime().toString(), method: 'get', }); }

src/types/api/system/login.d.ts

// 验证码类型声明 export interface Captcha { img: string; uuid: string; }

src/views/login/index.vue

<template> <p>Hello...</p> </template> <script lang="ts" setup> import { getCurrentInstance } from 'vue'; // 组件实例 const { proxy }: any = getCurrentInstance(); // 获取验证码 async function handleCaptcha() { const res = await proxy.$api.sys_login.getCaptcha() console.log('res:', res); } handleCaptcha() </script>

十二、全局组件1、全局组件注册

src/components/index.ts

const modulesFiles = import.meta.globEager('./*/*.vue'); const modules: any = {}; for (const key in modulesFiles) { const moduleName = key.replace(/(.*\/)*([^.] ).*/gi, '$2'); const value: any = modulesFiles[key]; modules[moduleName] = value.default; } // console.log(666, modules); export default modules;

main.ts

const app = createApp(App); // 全局组件注册 import myComponent from '@/components/index' Object.keys(myComponent).forEach((key) => { app.component(key, myComponent[key]) }) app.mount('#app')

2、组件示例

src/components/base/BaseNoData.vue

<template> <div class="no-data" :class="{ 'no-data-dark': true }"> <slot>暂无数据</slot> </div> </template> <style lang="scss" scoped> .no-data { padding: 100px 0; background: #fff; font-size: 28px; color: #999; text-align: center; &.no-data-dark { background: $dark_main_color; color: #fff; } } </style>

引用

<base-no-data>请先选择数据</base-no-data>


今日分享语句: 愿我可以一直沉迷于学习无法自拔。

,