vue3+eslint(推荐配置)|prettier+elementplus+国际化+axios封装+pinia

最近菜鸟自己搭建一个项目,想着 vue3 都出来这么久了,再不用 vue3,怕是等我熟悉 vue3 的时候,vue4 都出来了,直接落后时代一个大版本那就难受了!

但是突然从 vue2 转变到用这个 vue3 确实是让人脑壳疼,接下来就把菜鸟踩坑的地方都分享给大家了!

创建项目 + eslint + prettier

首先要知道的就是,既然是公司,那么一般都有一套代码规范,但是菜鸟现在待的是一个生物公司,之前都没有前端这个岗位,所以一切就是靠菜鸟了,那既然是由我定规范,那一定要搞好点,所以就采用了eslint + prettier!

之所以不用 vite,其一就是感觉 webpack 生态比较好,其二 vue2 就是 webpack 的,其三 vite 的一些依赖要自己去下载,感觉是没有 webpack 方便,所以暂时就没转换(不过好像没法体验 Pinia,可以自己把 vuex 卸载并 npm Pinia,也可以创建的时候不选 vuex)!

创建项目

vue create testpro

然后选择

在这里插入图片描述

之所以不用 ts,首先菜鸟不是很会 ts,第二菜鸟感觉 js 本来就是自由的语言,你给它加上限制不能说不好,只是菜鸟不喜欢而已!

然后路由模式一定要使用 hash 模式,history 模式菜鸟没用过,反正好像是要后端进行配合,不是很好使用

在这里插入图片描述

然后 css 菜鸟选择的是 scss,这个按照个人喜好就行!

eslint 菜鸟就是选择的 eslint + prettier

在这里插入图片描述

这个菜鸟选的第一个,不想让其提交的时候使用 fix, fix虽好,但是还是自己能全权掌握的更香,而不是交给 fix !

在这里插入图片描述

到这里项目就创建完了,但是配置还没有配置好!

eslint + prettier

因为这个时候,只要你新建一个 .vue 文件,eslint 就跑过来恶心你了,会报错

Component name "xxxxx” should always be multi-word

在这里插入图片描述

但是感觉这个规范确实不需要,所以只需要在 eslint.js 里面加上这一行就行

在这里插入图片描述

方便复制:

"vue/multi-word-component-names": 0,

这个时候你以为一切顺利,然后再敲了几行代码,eslint 就又来了,会报错

Delete eslint(prettier/prettier)

这是因为
在这里插入图片描述

这里就要创建 .prettierrc 文件,并输入以下

{
  "endOfLine": "auto"
}

注意

一定要重启vscode,不然还是会报错!

这里还有一篇文章说的是另一种方法,读者可以尝试:优雅解决:(linebreak-style) Expected linebreaks to be ‘LF‘ but found ‘CRLF‘. (eslint)

这个时候确实万事大吉,但是你如果写的代码最后一行没有换行,那么就会报错

Insert ␍⏎eslintprettier/prettier

但是这个无伤大雅,打个回车就行了!

完整 eslint 配置

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ["plugin:vue/vue3-essential", "eslint:recommended", "plugin:prettier/recommended"],
  parserOptions: {
    parser: "@babel/eslint-parser",
  },
  rules: {
    // 默认自带的,vite创建项目没有
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    // 菜鸟加的
    "vue/multi-word-component-names": 0,
    "no-unused-vars": "warn", // 避免不使用的变量,一定要删除才能跑
  },
  // 解决一些 definexxx 编译宏总提示未引入问题
  env: {
    node: true,
    'vue/setup-compiler-macros': true
  }
};

自动格式化

要使用 eslint 自然需要一个插件(eslint 不像 prettier,prettier 就算没有插件也可以正常使用,不过不能按照 prettier 的规则自动保存罢了):

在这里插入图片描述

在这里插入图片描述

但是如果想保存的时候自动格式化成为我们想要的格式,那么我们该怎么配置?

这个时候就要下载该插件:

在这里插入图片描述

并将 vscode 的 setting.json 设置成这样:

{
  // 让函数(名)和后面的括号之间加个空格
  "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
  // tab 大小为2个空格
  "editor.tabSize": 2,
  // 显示 markdown 中英文切换时产生的特殊字符
  "editor.renderControlCharacters": true,
  // 用来忽略工程打开的文件夹
  "files.exclude": {
    "**/.vscode": true,
    "**/.DS_Store": true,
    "**/.history": true,
    "**/nbproject": true
  },
  // 失去焦点自动保存
  "files.autoSave": "onFocusChange",
  // 资源管理器确认删除
  "explorer.confirmDelete": false,
  // 新窗口尺寸
  "window.newWindowDimensions": "maximized",
  // typescript更新启用文件移动时导入(就是ts文件移动时重新导入)
  "typescript.updateImportsOnFileMove.enabled": "always",
  // 比较不同时,忽略空白
  "diffEditor.ignoreTrimWhitespace": false,
  "editor.fontWeight": "normal",
  //忽略搜索设置
  "search.exclude": {
    "**/dist": true,
    "**/node_modules/**": true
  },
  // 将prettier设置为默认格式化程序(在编辑器中有可能被其他Formatter占用,所以将prettier设置为默认Formatter)
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  // 保存时自动格式化 (根据根目录下‘.prettierrc文件配置项’)
  "editor.formatOnSave": true,
  // 100 列后换行(去掉)
  "editor.wordWrapColumn": 100,
  // prettier 最长100列(去掉)
  "prettier.printWidth": 100,
  // prettier 结束随便(去掉)
  "prettier.endOfLine": "auto"

  // 配置 @/~@ 提示的配置,见博客:https://blog.csdn.net/langwang_100/article/details/130721122
  "path-intellisense.mappings": {
    "@": "${workspaceRoot}/src",
    "~@": "${workspaceRoot}/src"
  },
}

设置多少换行 – 》 .prettierrc

设置完这个,菜鸟以为完事大吉了,直到开发时,发现每次保存时,其实长度都没到 100 行就报错需要换行了,仔细研究才发现,.prettierrc 也需要设置:

{
  "endOfLine": "auto",
  "printWidth": 100
}

不知道为什么,setting 里面设置最长只有100,并没有效果!所以可以 去掉这几个配置

  // prettier 最长100列
  "prettier.printWidth": 100,
  // prettier 结束随便
  "prettier.endOfLine": "auto"
   // 100 列后换行
  "editor.wordWrapColumn": 100,

注意:

如果是已经创建好的项目,就要按照别人设置的 eslint 规则去新建并配置 .prettierrc,不然一保存,git 就全是改变!

element plus

使用 element plus 自然要使用其最强大的按需引入,全部引入实在是太浪费了!

首先我们按照官网的步骤:

npm install -D unplugin-vue-components unplugin-auto-import

vue.config.js

按照完事之后,就要配置 webpack 了,对于 webpack 好的人可能不难,但是不好的可能就不太会,所以这里菜鸟还是写一下,官网的 webpack 需要放在 vue.config.js 里面:

const { defineConfig } = require("@vue/cli-service");

// 按需引入element plus
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");

const port = 8888;

module.exports = defineConfig({
  publicPath:
    process.env.NODE_ENV === "production"
      ? "./" // 生产环境 --> 一般./就行,但是如果服务器有路径要改为:/pathname/
      : "/", // 开发环境
  transpileDependencies: true,// 将第三方包进行兼容转译,但是vite没有(因为大部分第三方包都自己转译好了,可以删除)
  productionSourceMap: false, // 指定在生产环境下是否生成源代码映射文件
  // 按需引入element plus
  configureWebpack: {
    resolve: {
      alias: {
        // 设置路径别名
        components: "@/components",
      },
    },
    plugins: [
      AutoImport({
        resolvers: [ElementPlusResolver()],
      }),
      Components({
        resolvers: [ElementPlusResolver()],
      }),
    ],
  },
  devServer: {
    port,
    proxy: {
      "/Api": {
        target: "http://xxxxx", // API服务器的地址
        changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
        pathRewrite: {
          "^/Api": "",
        },
        // headers 是用来设置请求头的字段,referer 字段用于指定请求的来源地址,即告诉后端服务器这个请求的来源是什么(其实可以不要,像下面一个一样)
        headers: {
          referer: "http://xxxx", // 转发接口
        },
      },
      "/aaa": {
        target: "xxxxx", // API服务器的地址
        ws: true, // 代理websockets,及不仅http请求会被代理,ws也会
        changeOrigin: true,
        pathRewrite: {
          "^/bena": "",
        },
      },
    },
  },
});

这里publicPath的配置倒是一场文学,菜鸟也不是很清楚,反正菜鸟上述的可以满足大部分没有特殊情况的开发者,具体详情见

在这里插入图片描述

在这里插入图片描述

然后等你兴高采烈的准备大干一场,并在项目中使用 element plus 时坑就来了,其编译的时候就会报错:

Module not found: Error: Can’t resolve ‘element-plus/es’ in
Module not found: Error: Can’t resolve ‘element-plus/es/components/base/style/css’ in

这个问题就是你没有安装 element plus 只安装了两个自动导入的插件而已,所以还要执行

npm install element-plus --save

注意

这里内容很多,下方几个常见报错菜鸟单独提了一个文章:element plus使用问题

1、有时候会报错 not a function

AutoImport is not a function
    at Object.<anonymous> (F:\pro\plantweb\vue.config.js:40:7)
    at Module._compile (node:internal/modules/cjs/loader:1198:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10)
    at Module.load (node:internal/modules/cjs/loader:1076:32)

Components is not a function
    at Object.<anonymous> (F:\pro\plantweb\vue.config.js:43:7)
    at Module._compile (node:internal/modules/cjs/loader:1198:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10)
    at Module.load (node:internal/modules/cjs/loader:1076:32)

这个时候就要降低版本,具体参考:vue 3.0 使用element-plus按需导入方法和报错解决

2、使用 ElMessage 报错

如果你在 script 中使用了 ElMessage ,那么eslint 会报错没有引入,但是其实是没问题的,只需要在 ElMessage 之前加上该代码:

// eslint-disable-next-line

注意

自动导入后,如果还是按照

import { ElMessage, ElMessageBox  } from "element-plus"

那么样式方面会有问题,主要是样式没有引入的问题,所以不要两个相互交叉使用!(图标引入还是要import,不要搞混!!!)

更优雅的解决办法

这里就要修改配置文件(顺便还有 element plus icon 的配置也在这里,后续就不再重复了),这里菜鸟用vite.config.js来说明,主要是webpack的不太清楚(应该类似),读者可以评论补充!

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    VueSetupExtend(),
    AutoImport({
      // 解决 ElMessage 报错问题,有时候编译器反应不过来,但是没问题,重启编译器就行
      eslintrc: {
        enabled: true
      },
      resolvers: [
        ElementPlusResolver(), // 自动导入图标组件
        IconsResolver({
          prefix: 'Icon'
        })
      ],
      // 自动导入后面这些包里面的东西,避免每次都要导入router、ref等,有时候编译器反应不过来,但是没问题,重启编译器就行
      imports: ['vue', 'vue-router', '@vueuse/core']
    }),
    Components({
      resolvers: [
        // 自动注册图标组件 --> 自动导入的图标要使用i-ep-xxx/i-ep-xxx-xxx
        IconsResolver({
          enabledCollections: ['ep']
        }),
        ElementPlusResolver()
      ]
    }),
    Icons({
      autoInstall: true
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    host: '0.0.0.0', // 允许内网访问
    // port: "" // 端口
  }
})

3、 element plus 版本过高

有的时候 element plus 版本高了也会报错,菜鸟没遇见,读者可以见:vue3引入element-plus报错解决方案

4、警告Feature flag VUE_PROD_HYDRATION_MISMATCH_DETAILS is not explicitly defined.

vue.config.js 添加上该代码:

chainWebpack: (config) => {
  config.plugin("define").tap((definitions) => {
    Object.assign(definitions[0], {
      __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
    });
    return definitions;
  });
},

参考:Vue3.4+报Feature flag VUE_PROD_HYDRATION_MISMATCH_DETAILS is not explicitly defined… 处理

5、报错 ResizeObserver loop completed with undelivered notifications.

需要在app.vue中加入该代码:

const _ResizeObserver = window.ResizeObserver;
window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
  constructor(callback) {
    callback = debounce(callback, 100); // 防抖函数自己写
    super(callback);
  }
};

参考:关于用element-ui中碰到的ERROR ResizeObserver loop completed with undelivered notifications.问题

防抖函数参考:vue3常用代码

element plus icon

使用 element plus icon 就和使用其他组件是一样的,唯一的区别就是要引入

import { } from “@element-plus/icons-vue”;

具体引入什么就是去官网点击图标,将复制下来的引入就行!

注意:

上面 npm 了 element-plus,那么这里的图标可以直接引用,不需要 npm 图标库了!

还有一个坑的地方就是按需引入,但是菜鸟发现按需引入确实可以,但是使用的是时候就不能是官网复制下来的,而是不知道哪里复制的,所以暂时不推荐使用!

按需引用参考:

1、Element Plus Icon图标自动引入

2、Vue3!ElementPlus!更加优雅的使用Icon

按需引用

菜鸟在后续使用中还是使用了按需引入,但是其实并不好用!!!

虽然知道了配置(就上面的 更优雅的解决办法 里面提供了),而且知道使用就是把icon的名字改成i-ep-iconname / i-ep-iconname-iconname(两个单词组成的就要变成这样)

但是如果要在el-button里面这样使用

<el-button type="primary" :icon="Search" @click="search">搜索</el-button>

只能引入,无法使用i-eq

element plus 按需导入设置为中文

菜鸟在开发过程中,发现这些 element plus 组件全部默认都是英文,虽然很简单,改不改都无所谓,但是还是要想想怎么解决!

只需要在app.vue中加入这么一行代码就行:

<template>
  <el-config-provider :locale="zhCn">
    <router-view />
  </el-config-provider>
</template>

<script setup>
// 引入element plus中文包
import zhCn from "element-plus/lib/locale/lang/zh-cn";
</script>

国际化

首先便是安装国际化库

npm i vue-i18n

在项目中,我们一般会新建一个文件夹专门存放 i18n 的配置

目录结构如下:
在这里插入图片描述

然后新建 i18n.js 文件,引入 vue-i18n,vue-i18n 使用 createI18n 创建实例:

import { createI18n } from "vue-i18n";
import EN from "./en/en";
import CN from "./cn/cn";

const message = {
  cn: {
    ...CN,
  },
  en: {
    ...EN,
  },
};

const i18n = createI18n({
  locale: "cn", // 设置语言类型
  legacy: false, // 如果要支持compositionAPI,此项必须设置为false;
  globalInjection: true, // 全局注册$t方法
  messages: message,
});

export default i18n;

然后 cn / en 里面的文件结构如下:

在这里插入图片描述

内容由自己定义,这里给个例子:

export default {
  username: "用户名",
  password: "密码",
  passwordtip: "请输入密码",
  signIn: "登录",
  signOut: "退出登录",
  signInerr: "登录失败",
};

英文的与之对应即可!

然后便是在 main.js 中引入国际化

在这里插入图片描述

注意

1、如果要在 script 中使用国际化方法,需要结构出 t 方法,html 中可以直接使用 $t(),如下:

import { useI18n } from "vue-i18n";

//  解构出t方法
const { t } = useI18n(); // 先调用此方法,然后再使用

// eslint-disable-next-line
ElMessage({
   message: t("signInerr"),
   type: "error",
 });

2、如果要获取值,必须这样使用:

js代码:

// 切换中英文
import { useI18n } from "vue-i18n";
const { locale } = useI18n(); // 先调用此方法,然后再使用
const changeLang = (msg) => {
  locale.value = msg;
  localStorage.setItem("LANG", msg);
};
const options = [
  {
    value: "cn",
    label: "中文",
  },
  {
    value: "en",
    label: "英文",
  },
];

html:

<el-select v-model="locale" @change="changeLang">
  <el-option
    v-for="item in options"
    :key="item.value"
    :label="item.label"
    :value="item.value"
  />
</el-select>

3、在非 setup 中使用

import i18n from "@/i18n/i18n";
const { t } = i18n.global;

其他就一样了,直接使用就行,这里的路径记得改成自己的!

axios 封装

// 对你引用的第三方网络请求框架进行封装
import axios from "axios";
// 这里的 router 引入和 setup 中不一样
import router from "@/router";

function signout() {
  localStorage.removeItem("token");
  localStorage.removeItem("Auth");
  router.replace({
    path: "/",
  });
}

if (process.env.NODE_ENV == "development") {
  axios.defaults.baseURL = "/Api";
} else if (process.env.NODE_ENV == "production") {
  axios.defaults.baseURL = "/pfps";
}

export function request(config) {
  // 1. 创建axios的实例
  const instance = axios.create({
    timeout: 30000,
  });
  //2. 使用axios拦截器
  //2.1 请求拦截
  instance.interceptors.request.use(
    (conF) => {
      //如果不返回,则真正的请求被拦截了,用户就会打印err
      // *** 一般进行的操作 ***
      // 1. config中的一些信息不符合服务器要求,就可以在这修改,在创建实例时也可以
      // 2. 每次发送网络请求时,都希望在界面中显示一个请求的图标
      // 3. 某些网络请求(登陆[token]),必须携带一些特殊的信息
      // console.log(conF);
      const token = localStorage.getItem("token"); // 也可能放在别的地方,按需修改,eg:token ? (conF.headers["Authorization"] = token) : null;
      token ? (conF.headers.token = token) : null;
      // 特殊界面无需token
      if (
        conF.url.includes("/form/information") ||
        conF.url.includes("/user/upload") ||
        conF.url.includes("/user/downloadExcel") ||
        conF.url.includes("/form/update")
      ) {
        conF.headers.token = null;
      }
      return conF;
    },
    (err) => {
      console.log(err);
      // eslint-disable-next-line
      ElMessage({
        showClose: true,
        message: err,
      });
    }
  );
  //2.2 响应拦截
  instance.interceptors.response.use(
    (res) => {
      console.log(res);
      //更新token
      if (res.status == 200) {
        if (res.headers?.token) {
          localStorage.setItem("token", res.headers.token);
          // console.log(res.headers.token);
        }
      }
      // 这个是我们公司后端不规范导致,响应是200,但是code却是其他
      if (res.data.code) {
        switch (res.data.code) {
          // 可继续添加,按下面error中
          case 401:
            // eslint-disable-next-line
            ElMessage({
              showClose: true,
              message: "登录过期,请重新登录",
            });
            signout();
            break;
        }
      }
      return res.data;
    },
    (error) => {
      console.log(error);
      if (error.response.status) {
        switch (error.response.status) {
          // 401: 未登录
          case 401:
            // eslint-disable-next-line
            ElMessage({
              showClose: true,
              message: "未登录",
            });
            signout();
            break;
          // 403 token过期
          // 登录过期对用户进行提示
          // 清除本地token对象
          // 跳转登录页面
          case 403:
            // eslint-disable-next-line
            ElMessage({
              showClose: true,
              message: "登录过期,请重新登录",
            });
            signout();
            break;
          // 404请求不存在
          case 404:
            // eslint-disable-next-line
            ElMessage({
              showClose: true,
              message: "网络请求不存在",
            });
            break;
          case 500:
            // eslint-disable-next-line
            ElMessage({
              showClose: true,
              message: "服务器错误",
            });
            break;
          // 其他错误,直接抛出错误提示
          default:
            console.log(error);
            // 下面会介绍
            if (error.response.data.byteLength && error.response.data.byteLength <= 100) {
              const jsonString = new TextDecoder("utf-8").decode(error.response.data);
              const jsonObject = JSON.parse(jsonString);
              // eslint-disable-next-line
              ElMessage({
                showClose: true,
                message: jsonObject.message,
              });
            } else {
              // eslint-disable-next-line
              ElMessage({
                showClose: true,
                message: error.response.data.message || error.response.message,
              });
            }
        }
        return Promise.reject(error.response);
      } else {
        console.log(error);
        // eslint-disable-next-line
        ElMessage({
          showClose: true,
          message: "服务器异常",
        });
      }
    }
  );
  //3. 真正的发送请求
  return instance(config);
}

注意

1、菜鸟这里在 res 里面进行判断了 res.data.code ,是因为后端未登录异常不是异常返回,而是返回200,然后 data 里面显示 401,所以只能在上面多加一个判断!

2、菜鸟 default 里面进行了一些 byte 类型的操作,是因为后端导出数据的时候,返回 pdf 数据,但是如果返回失败则是错误提示,所以无法分开搞,只能通过长度来判断!(其实不好)

切换pinia(后补上)

菜鸟在写完博客后,马上尝试了一下把 vuex 换成 pinia ,当然仅限创建项目的时候换,要是你都开发完了,那就不要换了!!!

首先就是执行两个命令:

npm install pinia -S
npm uninstall vuex

然后把 main.js 的store换成 pinia
在这里插入图片描述
然后如果是 vuex 换过来的就不用自己创建 store目录和 index.js 文件,否则需要自己创建,将里面的内容改成:

// store/index.js
import { defineStore } from "pinia";
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例

export const useMainStore = defineStore("main", {
  // 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
  // 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
  // 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
  state: () => {
    return {
      info: "pinia 可以使用",
    };
  },
  getters: {},
  actions: {},
});

// 2. 使用容器中的 state
// 3. 通过 getter 修改 state
// 4. 使用容器中的 action 同步和异步请求

项目中最简单的使用:

js代码

import { useMainStore } from "../../store";
const mainStore = useMainStore();

html代码

<div>{{ mainStore.info }}</div>

但是 vite 的 pinia 生成的和这里不一样。vite生成的如下(并没有state,getter等):

// pinia/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

pinia确实比vuex好用多了,直接修改也能监听到变化,yyds!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PBitW

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值