# vue3-template **Repository Path**: lianxuan7/vue3-template ## Basic Information - **Project Name**: vue3-template - **Description**: Vue3 + Vite2 + TypeScript面向企业级别开发环境脚手架模板 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: dev - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 8 - **Forks**: 1 - **Created**: 2022-03-25 - **Last Updated**: 2024-09-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: 项目初始化, 脚手架, 项目模板 ## README # Vue3 + Vite2 + TypeScript 面向企业级别开发项目环境 ## 介绍 本项目将帮助你搭建 vue3 面向企业级项目开发环境,用于提升前端开发速度及效率。已经做成脚手架上传至 npm,可自行下载`qian-cli`脚手架初始化项目 qian-cli 仓库地址: qian-cli ## 安装 1. 安装 qian-cli 脚手架 ```sh npm install qian-cli -g ``` 2. 使用脚手架初始化项目 ```sh qian-cli init v3-test ``` ## 技术栈 | 名字 | 说明 | | -------------- | ----------------------------- | | Vue3 | 构建用户界面的渐进式框架 | | Tsx Typescript | JavaScript 类型提示、语法扩展 | | Prettierrc | 格式化代码 | | Vite2 | 前端开发与构建工具 | | Scss | css 扩展框架 | | Vue-router4 | 路由 | | Axios | HTTP 请求 | | Pinia | 全局状态管理 | ## 版本号最新变化 ```sh # 2022/8/27 pinia ^2.0.18 → ^2.0.21 sass ^1.54.4 → ^1.54.5 vue-router ^4.1.3 → ^4.1.5 @types/node ^18.7.3 → ^18.7.13 typescript ^4.7.4 → ^4.8.2 unplugin-auto-import ^0.11.1 → ^0.11.2 vite ^3.0.7 → ^3.0.9 ``` ## 步骤 > 以下步骤均为仓库第一次创建的时候使用的步骤,后续仓库变化下方步骤不会变化 ### 1. 安装 vite 脚手架环境 ```sh npm init vite@latest ``` ### 2. 安装 Jsx 插件 ```sh npm i @vitejs/plugin-vue-jsx --dev ``` vite.config.ts 文件添加 ```ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Jsx from '@vitejs/plugin-vue-jsx' export default defineConfig({ plugins: [vue(), Jsx()] }) ``` ### 3. 安装 prettierrc 并配置 ```sh npm i eslint-config-prettier --dev ``` .prettierrc 文件添加 ```json { "useTabs": false, "tabWidth": 2, "printWidth": 300, "singleQuote": true, "trailingComma": "none", "bracketSpacing": true, "semi": false } ``` ### 4. Vite 别名与跨域反向代理 vite.config.ts 文件 ```ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Jsx from '@vitejs/plugin-vue-jsx' import { resolve } from 'path' // 如果报错则安装一下 npm i @types/node vscode报错需要关闭软件重启 export default defineConfig({ plugins: [vue(), Jsx()], server: { open: false, //自动打开浏览器 base: './ ', //生产环境路径 proxy: { '^/api': { target: 'http://localhost', // 后端服务实际地址 changeOrigin: true, //开启代理 rewrite: (path) => path.replace(/^\/api/, '') } } }, resolve: { alias: { '@': resolve(__dirname, './src') // 例: @/components/HelloWorld.vue } } }) ``` tsconfig.json 文件 ```json { "compilerOptions": { "target": "esnext", "useDefineForClassFields": true, "module": "esnext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "lib": ["esnext", "dom"], "baseUrl": ".", // 配置一 "paths": { // 配置二 "@/*": ["src/*"] } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] } ``` ### 5. Vite 全局变量 跟项目下新建`.env.development`文件和`.env.production`文件 ```js # .env.development NODE_ENV = development VITE_APP_BASEURL = '' VITE_APP_BASE_API = /api # .env.production NODE_ENV = production VITE_APP_BASEURL = '' VITE_APP_BASE_API = www.xuanxiaoqian.com // 使用 import.meta.env.VITE_APP_BASE_API ``` Ts 联想提示,在`src`文件下新增`dev.d.ts`文件(如有则修改就行) ```ts /// declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } interface ImportMetaEnv extends Readonly> { // 更多环境变量... readonly VITE_APP_BASE_API: string } interface ImportMeta { readonly env: ImportMetaEnv } ``` 配置 `package.json` , 打包区分开发环境和生产环境 ```sh "build:dev": "vite build --mode development", "build:pro": "vite build --mode production", ``` ### 6. css 预处理器 scss ```sh npm i sass --dev ``` Vite 设置全局 scss 变量 在`style`文件下新增`variable.scss`文件 ```scss // src/style/variable.scss $vua-pre: vua-; $white-color: #fff; $border-color: #dcdee2; $dash-border-color: #f0f0f0; $primary-color: #2d8cf0; $assist-color: #bae7ff; $disable-color: #c5c8ce; $text-color: #333; $error-color: #e44233; $bg-prev-color: #f5f7fa; $font-size-base: 14px; $font-size-mid: $font-size-base + 2; $font-size-large: $font-size-base + 4; $font-size-huge: $font-size-base + 10; ``` 新增`vite.config.ts`文件内容 ```ts export default defineConfig({ plugins: ... server: ... resolve: ... css: { preprocessorOptions: { scss: { additionalData: `@import "/@/style/variable.scss";` // scss全局变量 } } } }) ``` 使用全局变量 ```scss .home { color: $assist-color; } ``` ### 7. Vue-router 路由 ```sh npm i vue-router@4 ``` `src`文件下新增`router`文件夹和`views`文件夹 ```ts // router/index.ts import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('/@/views/Home/Home.vue'), // 需要创建这个文件,不然启动会报错 meta: { title: '404not found' } }, { path: '/', name: 'Home', component: () => import('/@/views/Home/Home.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes: routes, strict: true, scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { top: 0 } } } }) // 路由白名单 const whiteList = ['/', '/index', '/home', '/login', '/register'] router.beforeEach((to, from, next) => { // if (whiteList.indexOf(to.path) === -1) { // 如果有需要就打开 // next({ path: '/' }) // } else { // next() // } }) router.afterEach(() => {}) export default router ``` 修改入口文件`main.ts` ```ts import { createApp } from 'vue' import App from './App.vue' import router from './router/index' const app = createApp(App) app.use(router) app.mount('#app') ``` 修改主页面`App.vue` ```vue ``` ### 8. axios 数据请求 ```sh npm i axios ``` `src`文件下新增`utils`文件夹并在下面再建一个`http`文件夹 src/utils/http ```js // src/utils/http/defaultConfig.ts import axios, { AxiosRequestConfig } from 'axios' export const defaultConfig: AxiosRequestConfig = { baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 1000, headers: { token: localStorage.getItem('token') ?? '', 'Content-Type': 'application/json;charset=UTF-8' } } export default defaultConfig ``` ```ts // src/utils/http/interceptors.ts import Axios, { AxiosInstance } from 'axios' import defaultConfig from './defaultConfig' export class Interceptors { public instance: AxiosInstance constructor() { this.instance = Axios.create(defaultConfig) this.init() } init() { // 数据请求之前 this.instance.interceptors.request.use( (config: any) => { // console.log("请求了"); return config }, (err) => { console.log(err) } ) // 数据返回之前 this.instance.interceptors.response.use( (response) => { // console.log("响应了"); return Promise.resolve(response) }, (err) => { console.log(err) } ) } getInterceptors() { return this.instance } } const instance = new Interceptors().getInterceptors() export default instance ``` ```ts // src/utils/http/index.ts import { AxiosInstance } from 'axios' import instance from './interceptors' const http: AxiosInstance = instance export default http ``` `src`文件下新增`api`文件夹并在下面再建一个`login`文件夹 src/api/login ```ts // src/api/login/types.ts export interface LoginParams { userName: string passWord: string | number } export interface LoginApi { login: (params: LoginParams) => Promise } ``` ```ts // src/api/login/login.ts import http from '/@/utils/http' import * as T from './types' const loginApi: T.LoginApi = { login(params) { return http.post('/login', params) } } export default loginApi ``` 使用 ```ts import loginApi from '/@/api/login/login' loginApi.login({ userName: 'root', passWord: '123456' }).then((res) => { console.log(res.data) }) ``` ### 9. pinia 状态管理 ```sh npm i pinia@next ``` `src`文件下新增`store`文件夹 ```ts // src/store/main.ts import { defineStore } from 'pinia' export const useMainStore = defineStore({ id: 'mian', state: () => ({ name: '超级管理员' }) }) ``` 修改入口文件`main.ts` ```TS import { createApp } from 'vue' import App from './App.vue' import router from './router/index' import { createPinia } from 'pinia' const pinia = createPinia() const app = createApp(App) app.use(router) app.use(pinia) app.mount('#app') ``` 使用 ```vue ``` ### 10. reset.css 样式统一化 `src`文件下新增`style`文件夹 ```css // src/style/reset.css /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } ``` 修改入口文件`main.ts` ```ts import { createApp } from 'vue' import App from './App.vue' import router from './router/index' import { createPinia } from 'pinia' // reset.css import './style/reset.css' const pinia = createPinia() const app = createApp(App) app.use(router) app.use(pinia) app.mount('#app') ``` ### 11. hooks 代码复用 `src`文件下新增`hooks`文件夹 ```ts // src/hooks/usePoint.ts import { reactive, onMounted, onBeforeUnmount } from 'vue' export default function () { //展示的数据 可以通过App.vue 界面去隐藏 let point = reactive({ x: 0, y: 0 }) //获取鼠标点击事件 function savePonint(event: MouseEvent) { point.x = event.pageX point.y = event.pageY console.log(event.pageX, event.pageY) } //现实之后调用 挂载完毕 onMounted(() => { window.addEventListener('click', savePonint) }) //在隐藏之前调用 卸载之前 onBeforeUnmount(() => { window.removeEventListener('click', savePonint) }) return point } ``` 使用 ```ts import usePoint from '/@/hooks/usePoint' let point = usePoint() console.log(point) ``` ### 12. Vue Api 自动导入 ```sh npm i unplugin-auto-import -D ``` 新增`vite.config.ts`文件内容 ```ts import AutoImport from 'unplugin-auto-import/vite' export default defineConfig({ plugins: [ AutoImport({ imports: ['vue', 'vue-router'], // 可以选择auto-import.d.ts生成的位置,使用ts建议设置为'src/auto-import.d.ts' dts: 'src/auto-import.d.ts' }) ] }) ``` 重启项目`src`目录下会新增`auto-import.d.ts`文件,我们就可以愉快的自动导入啦 ## X. TODO `以下的内容均不在仓库中,只是为了方便有需要的人能快速使用` ### setup 语法糖下写组件 name ```sh npm i vite-plugin-vue-setup-extend -D ``` 配置`vite.config.ts` ```ts import { defineConfig } from 'vite' import VueSetupExtend from 'vite-plugin-vue-setup-extend' export default defineConfig({ plugins: [VueSetupExtend()] }) ``` 使用 ```ts ``` ### Element-Plus 组件库 > https://element-plus.gitee.io/zh-CN/guide/installation.html ```sh npm install element-plus --save ``` 修改入口文件`main.ts` ```TS import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app') ``` 使用 ```vue 看见我没问题就成功了!!! ``` ### VueUse hooks 库 > https://vueuse.org/ ### Nprogress 网站进度条 > https://ricostacruz.com/nprogress/ ```sh npm install --save nprogress ``` ```ts import Nprogress from 'nprogress' import 'nprogress/nprogress.css' Nprogress.configure({ // 动画方式 easing: 'ease', // 递增进度条的速度 speed: 500, // 是否显示加载ico showSpinner: false, // 自动递增间隔 trickleSpeed: 200, // 初始化时的最小百分比 minimum: 0.3 }) export default Nprogress ``` 使用 ```ts import NProgress from '../utils/nprogress' NProgress.start() // 控制开始加载 NProgress.done() // 控制结束加载 ``` ### JsCookie 本地 cookie > https://www.npmjs.com/package/js-cookie ```sh npm install js-cookie --save ``` 使用 ```ts import Cookies from 'js-cookie' // Create a cookie, valid across the entire site: Cookies.set('name', 'value') // 创建一个从现在起7天内过期的cookie,在整个站点有效: Cookies.set('name', 'value', { expires: 7 }) // Create an expiring cookie, valid to the path of the current page: Cookies.set('name', 'value', { expires: 7, path: '' }) // name value Cookies.set('TokenKey', token, { expires: 1000, path: '/', domain: 'xx.com' }) //不写过期时间,默认为1天过期 this.$cookies.set('user_session', '25j_7Sl6xDq2Kc3ym0fmrSSk2xV2XkUkX') this.$cookies.set('token', 'GH1.1.1689020474.1484362313', '60s') // 60秒后过去 this.$cookies.set('token', 'GH1.1.1689020474.1484362313', '30MIN') // 30分钟后过去 // Read cookie: Cookies.get('name') // => 'value' Cookies.get('nothing') // => undefined // Read all visible cookies: Cookies.get() // => { name: 'value' } // Delete cookie: Cookies.remove('name') // Delete a cookie valid to the path of the current page: Cookies.set('name', 'value', { path: '' }) Cookies.remove('name') // fail! Cookies.remove('name', { path: '' }) // removed! ``` ### CSScomb Css 样式顺序 > vscode 插件 CSScomb,用于保存自动修改 css 属性顺序 下载`CSScomb `插件 在项目根目录添加新文件`.csscomb.json` ```json { "exclude": [".git/**", "node_modules/**", "bower_components/**"], "always-semicolon": true, "block-indent": " ", "color-case": "lower", "color-shorthand": true, "element-case": "lower", "eof-newline": true, "leading-zero": false, "quotes": "single", "remove-empty-rulesets": true, "space-after-colon": " ", "lines-between-rulesets": 1, "space-after-combinator": " ", "space-after-opening-brace": "\n", "space-after-selector-delimiter": "\n", "space-before-closing-brace": "\n", "space-before-colon": "", "space-before-combinator": " ", "space-before-opening-brace": " ", "space-before-selector-delimiter": "", "space-between-declarations": "\n", "strip-spaces": true, "unitless-zero": true, "vendor-prefix-align": true, "sort-order": [ [ "font", "font-family", "font-size", "font-weight", "font-style", "font-variant", "font-size-adjust", "font-stretch", "font-effect", "font-emphasize", "font-emphasize-position", "font-emphasize-style", "font-smooth", "line-height", "position", "z-index", "top", "right", "bottom", "left", "display", "visibility", "float", "clear", "overflow", "overflow-x", "overflow-y", "-ms-overflow-x", "-ms-overflow-y", "clip", "zoom", "-webkit-align-content", "-ms-flex-line-pack", "align-content", "-webkit-box-align", "-moz-box-align", "-webkit-align-items", "align-items", "-ms-flex-align", "-webkit-align-self", "-ms-flex-item-align", "-ms-grid-row-align", "align-self", "-webkit-box-flex", "-webkit-flex", "-moz-box-flex", "-ms-flex", "flex", "-webkit-flex-flow", "-ms-flex-flow", "flex-flow", "-webkit-flex-basis", "-ms-flex-preferred-size", "flex-basis", "-webkit-box-orient", "-webkit-box-direction", "-webkit-flex-direction", "-moz-box-orient", "-moz-box-direction", "-ms-flex-direction", "flex-direction", "-webkit-flex-grow", "-ms-flex-positive", "flex-grow", "-webkit-flex-shrink", "-ms-flex-negative", "flex-shrink", "-webkit-flex-wrap", "-ms-flex-wrap", "flex-wrap", "-webkit-box-pack", "-moz-box-pack", "-ms-flex-pack", "-webkit-justify-content", "justify-content", "-webkit-box-ordinal-group", "-webkit-order", "-moz-box-ordinal-group", "-ms-flex-order", "order", "-webkit-box-sizing", "-moz-box-sizing", "box-sizing", "width", "min-width", "max-width", "height", "min-height", "max-height", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "table-layout", "empty-cells", "caption-side", "border-spacing", "border-collapse", "list-style", "list-style-position", "list-style-type", "list-style-image", "content", "quotes", "counter-reset", "counter-increment", "resize", "cursor", "-webkit-user-select", "-moz-user-select", "-ms-user-select", "user-select", "nav-index", "nav-up", "nav-right", "nav-down", "nav-left", "-webkit-transition", "-moz-transition", "-ms-transition", "-o-transition", "transition", "-webkit-transition-delay", "-moz-transition-delay", "-ms-transition-delay", "-o-transition-delay", "transition-delay", "-webkit-transition-timing-function", "-moz-transition-timing-function", "-ms-transition-timing-function", "-o-transition-timing-function", "transition-timing-function", "-webkit-transition-duration", "-moz-transition-duration", "-ms-transition-duration", "-o-transition-duration", "transition-duration", "-webkit-transition-property", "-moz-transition-property", "-ms-transition-property", "-o-transition-property", "transition-property", "-webkit-transform", "-moz-transform", "-ms-transform", "-o-transform", "transform", "-webkit-transform-origin", "-moz-transform-origin", "-ms-transform-origin", "-o-transform-origin", "transform-origin", "-webkit-animation", "-moz-animation", "-ms-animation", "-o-animation", "animation", "-webkit-animation-name", "-moz-animation-name", "-ms-animation-name", "-o-animation-name", "animation-name", "-webkit-animation-duration", "-moz-animation-duration", "-ms-animation-duration", "-o-animation-duration", "animation-duration", "-webkit-animation-play-state", "-moz-animation-play-state", "-ms-animation-play-state", "-o-animation-play-state", "animation-play-state", "-webkit-animation-timing-function", "-moz-animation-timing-function", "-ms-animation-timing-function", "-o-animation-timing-function", "animation-timing-function", "-webkit-animation-delay", "-moz-animation-delay", "-ms-animation-delay", "-o-animation-delay", "animation-delay", "-webkit-animation-iteration-count", "-moz-animation-iteration-count", "-ms-animation-iteration-count", "-o-animation-iteration-count", "animation-iteration-count", "-webkit-animation-direction", "-moz-animation-direction", "-ms-animation-direction", "-o-animation-direction", "animation-direction", "text-align", "-webkit-text-align-last", "-moz-text-align-last", "-ms-text-align-last", "text-align-last", "vertical-align", "white-space", "text-decoration", "text-emphasis", "text-emphasis-color", "text-emphasis-style", "text-emphasis-position", "text-indent", "-ms-text-justify", "text-justify", "letter-spacing", "word-spacing", "-ms-writing-mode", "text-outline", "text-transform", "text-wrap", "text-overflow", "-ms-text-overflow", "text-overflow-ellipsis", "text-overflow-mode", "-ms-word-wrap", "word-wrap", "word-break", "-ms-word-break", "-moz-tab-size", "-o-tab-size", "tab-size", "-webkit-hyphens", "-moz-hyphens", "hyphens", "pointer-events", "opacity", "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", "-ms-interpolation-mode", "color", "border", "border-width", "border-style", "border-color", "border-top", "border-top-width", "border-top-style", "border-top-color", "border-right", "border-right-width", "border-right-style", "border-right-color", "border-bottom", "border-bottom-width", "border-bottom-style", "border-bottom-color", "border-left", "border-left-width", "border-left-style", "border-left-color", "-webkit-border-radius", "-moz-border-radius", "border-radius", "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius", "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius", "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius", "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius", "-webkit-border-image", "-moz-border-image", "-o-border-image", "border-image", "-webkit-border-image-source", "-moz-border-image-source", "-o-border-image-source", "border-image-source", "-webkit-border-image-slice", "-moz-border-image-slice", "-o-border-image-slice", "border-image-slice", "-webkit-border-image-width", "-moz-border-image-width", "-o-border-image-width", "border-image-width", "-webkit-border-image-outset", "-moz-border-image-outset", "-o-border-image-outset", "border-image-outset", "-webkit-border-image-repeat", "-moz-border-image-repeat", "-o-border-image-repeat", "border-image-repeat", "outline", "outline-width", "outline-style", "outline-color", "outline-offset", "background", "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", "background-color", "background-image", "background-repeat", "background-attachment", "background-position", "background-position-x", "-ms-background-position-x", "background-position-y", "-ms-background-position-y", "-webkit-background-clip", "-moz-background-clip", "background-clip", "background-origin", "-webkit-background-size", "-moz-background-size", "-o-background-size", "background-size", "box-decoration-break", "-webkit-box-shadow", "-moz-box-shadow", "box-shadow", "filter:progid:DXImageTransform.Microsoft.gradient", "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", "text-shadow" ] ] } ``` 在 文件-> 首选项-> 设置-> settings.json 添加 ```json "csscomb.formatOnSave": true, "csscomb.preset": {}, "csscomb.ignoreFilesOnSave": [ ], ``` 在 vscode 下 CTRL+Shift+P 唤出快捷命令,输入`CSSComb:Format styles`回车 (不能缺少) 修改 css 代码保存,就可以看到 css 属性顺序自动改变了 ### 更新项目 npm 依赖版本 > https://www.npmjs.com/package/npm-check-updates 用于检查项目`package.json`的依赖是否是最新版本 ```sh npm install -g npm-check-updates ``` 使用 ```sh ncu # 检查项目依赖最新版本 ncu -u # 更新package.json的版本号 npm install # 更新项目依赖 ``` ### 响应性语法糖 > https://cn.vuejs.org/guide/extras/reactivity-transform.html `此功能不建议使用,属于一个实验性功能,在此列出此功能是为了让不知道的人知道vue还有这个东西` 具体使用请点击上面链接前往 vue 官方文档学习使用 ### vue3 查漏补缺 > https://zhuanlan.zhihu.com/p/423860019 ### postcss > vite 默认添加 postcss,所以不需要额外安装 ```sh npm i autoprefixer postcss-plugin-px2rem -D # 第一个是浏览器前缀 第二个是rem移动端适配 ``` 在根目录添加`postcss.config.cjs` ```js module.exports = { plugins: [ require('autoprefixer'), require('postcss-plugin-px2rem')({ rootValue: 37.5, //换算基数,1rem相当于10px,值为37.5时,1rem为20px,淘宝的flex默认为1rem为10px // unitPrecision: 5, //允许REM单位增长到的十进制数字。 //propWhiteList: [], //默认值是一个空数组,这意味着禁用白名单并启用所有属性。 propBlackList: ['border'], //黑名单 // exclude: /(node_module)/, //默认false,可以(reg)利用正则表达式排除某些文件夹的方法,例如/(node_module)\/如果想把前端UI框架内的px也转换成rem,请把此属性设为默认值 // selectorBlackList: [], //要忽略并保留为px的选择器 // ignoreIdentifier: false, //(boolean/string)忽略单个属性的方法,启用ignoreidentifier后,replace将自动设置为true。 // replace: true, // (布尔值)替换包含REM的规则,而不是添加回退。 mediaQuery: false, //(布尔值)允许在媒体查询中转换px。 minPixelValue: 3 //设置要替换的最小像素值(3px会被转rem)。 默认 0 }) ] } ``` ## 注意事项 ### 1. Vite 代理事件 > 情况:我使用 vite 的 proxy 去代理我的请求,但是我发现好像并没有帮我代理 `事件重现`: 我在 vite.config 里面写了代理配置 ```ts server: { ... proxy: { '^/api': { target: 'http://localhost:8000', // 后端服务实际地址 changeOrigin: true, //开启代理 rewrite: (path) => path.replace(/^\/api/, '') } } }, ``` `src/utils/http/defaultConfig` ```ts import { AxiosRequestConfig } from 'axios' export const defaultConfig: AxiosRequestConfig = { baseURL: '/api' // 已经添加上了 } export default defaultConfig ``` `src/api/login/user/user.ts` ```ts import http from '/@/utils/http' export const allVideo = () => http.post('/user/login') ``` 然后我开心的去请求了,却发现浏览器报错 ```sh POST http://192.168.31.153:3000/api/user/login 404 (Not Found) Error: Request failed with status code 404 ... ``` 我一看,地址为什么是这样的,不应该是 `http://localhost:8000/user/login`吗?我以为是代理失效,找了半天才发现是我请求方式错了 `src/api/login/user/user.ts` ```ts import http from '/@/utils/http' export const allVideo = () => http.get('/user/login') // 改成get ``` 我们看成功的真实请求地址 ```sh 请求网址: http://192.168.31.153:3000/api/user/login 请求方法: GET ``` 是我 nginx 知识薄弱了,他的请求地址不会变。 但是为什么会现在才发现这个问题呢?可能是之前创建项目第一次请求都成功了,当后面的 api 写错了浏览器报 404 我就会潜意识认为是我 api 地址或请求方式写错了,但是这次是第一次请求就报错,让我以为是没有代理上。