Vue3实战操作技巧和经验教训


normalize.css 文件的安装

normalize.css 的作用是在项目编译打包的时候磨平各浏览器对 css 的不同展示带来的差异。
安装命令npm install mormalize.css --save
安装完成后,在main.js文件中导入。

import "normalize.css";

eslint 和 Prettier 实现代码统一规范

在管理前端代码过程中,或者在开发过程中,总是会有不符合规范的编码,如双引号和单引号的混合使用,没有格式化代码,没有层次感等等。这个时候 vue 中的一些插件给我们很好的解决了,目前了解的最好的方案就是eslint + Prettier实现。

配置过程:

  1. 首先需要在 vscode 上安装Prettier插件,直接在vscode插件市场里面找到安装即可。
  2. 项目本身需要使用eslint,如果在搭建脚手架的时候忘记安装eslint,可以通过npm安装,不做赘述。
  3. 在项目目录下,也就是vue.config.js文件所在的同级目录下,创建一个.prettierrc文件。
  4. .prettierrc文件中内容如下:
{
  "semi": true,
  "trailingComma": "none",
  "singleQuote": true
}
  • semi: 表示声明代码结尾使用分号,在 JAVA 语言中,都是要用分号结尾,表示一行代码或者代码块结束,在 js 中,可以不用分号,这是使用true,也可以不使用。看个人习惯,确定是否添加(作为 Java 程序猿,必须要加一下,强迫症);
  • singleQuote: 指定单引号,在 js 中字符串使用单引号和双引号都是可以的,但是一般都是使用单引号,因此这里设置此属性为true
  • trailingComma: 多行使用拖尾逗号,也就是说在定义多个属性或者方法的时候,最终结束需要使用逗号,最后一个方法或者属性不加逗号,这里使用 none,表示不加.

.prettierrc文件还有很多其他的配置,可以自行添加,不做赘述。

  1. 配置 vscode,实现保存文件的时候,自动按照配置的规则,对代码不符合要求的部分进行校验的修改。

    • 点击 vscode 的左下角设置,进入设置页面;
    • 在设置页面搜索save;
    • 找到Editor:Fromat On Save,将其选中。

通过以上配置即可使用啦!

注意问题:

  1. Prettiereslint存在冲突,Prettier 认为方法名和后面的括号应该放在一起,中间没有空格,但是 eslint 觉得方法和括号之间需要有空格,此时会报错,这个可以在.eslintrc.js文件中增加以下配置。(但是在高版本里面这个冲突问题好像已经不存在了)
'space-before-function-paren': 'off'
  1. 制表符在Prettier中是 2 个字符,而在 Vscode 默认是 4 个,需要在设置里面,将制表符修改成 2 个字符。找到Edit:Tab size选项,将其值改为 2。

  2. 如果存在多个代码格式化工具,要么卸载,要么通过右击文件内容区域,选择格式化文档将Prettier设置为默认即可。

推荐配置:

  1. .eslintrc.js文件的rules中添加一下'no-unused-vars': 'off',用于提示没有使用的变量或者导入的依赖没有用时候,都会报错,可以考虑关闭,错误提示会少一点,要是希望代码看起来更优雅,可以考虑开发完对代码进行优化时使用,开发过程中,还是建议关闭,有点烦。

axios 服务调用封装

使用步骤:

  1. 在使用前,需要通过npm命令安装axios工具。(npm install axios --save)
  2. 在实际使用中,需要在script标签内引用。(import axios from 'axios')

示例代码:

<script>
    import axios from 'axios';
    export default {
        // 各种复杂的时候,可以参考axios官网
        axios.get("http://www.baidu.com/xxx/xxx")
        .then((response) => {
            console.log(response);
        });
    }
</script>

axios 基本的封装:

src下创建一个utils目录,在目录中创建一个 axios 封装 js 文件。

// 导入axios依赖
import axios from "axios";

// 创建axios清楚基础的一些信息
const instance = axios.create({
  // 基础地址
  baseURL: "http://localhost:8080/",
  // 超时时间
  timeout: 100000,
});

// GET 请求,固定application/x-www-form-urlencoded格式
export const get = (url, params = {}) => {
  return new Promise((resolve, reject) => {
    instance.get(url, { params }).then(
      (response) => {
        resolve(response.data);
      },
      (error) => {
        reject(error);
      }
    );
  });
};

// Post请求,参数是application/json格式
export const postJson = (url, requestData) => {
  return post(url, requestData, "application/json");
};

// Post请求,参数是application/x-www-form-urlencoded格式
export const postForm = (url, requestData) => {
  return post(url, requestData, "application/x-www-form-urlencoded");
};

// post请求的公共部分抽离
const post = (url, requestData, contentType) => {
  return new Promise((resolve, reject) => {
    instance
      .post(url, requestData, {
        headers: {
          "Content-Type": contentType,
        },
      })
      .then(
        (response) => {
          resolve(response.data);
        },
        (error) => {
          reject(error);
        }
      );
  });
};

不同运行环境的环境变量配置

在实际的项目研发过程中,项目本地开发是一套环境,项目上线又是另一套环境,比如前端和后端的交互,这个时候为了不同环境下的配置方便切换,@vue/cli提供了三套环境,分别是developmenttestproduction。创建文件格式是.env.development,根据不同环境,切换后缀即可。

文件内容格式如下:

# 标识文件类型
NODE_ENV = 'production'
# 配置内容(配置项名称必须以VUE_APP_开头)
VUE_APP_BACKEND_BASE_URL = http://192.168.1.11/backend

在项目中使用方式:

const requestUrl = process.env.VUE_APP_BACKEND_BASE_URL + "/api/userList";

注意和优化点:

  1. 是需要注意的配置文件中,除了标识文件类型使用ENV或者NODE_ENV,其他配置项的名称都要以VUE_APP_开头,否则不会被识别。
  2. 我们正常的执行npm run serve就能运行项目,其实在内部是执行了vue-cli-service serve的,这个配置是在 package.json 文件中的 scripts 中,由于我们需要切换环境变量,每次执行命令指定配置文件比较麻烦,这里就可以通过修改 scripts 内的脚本来实现。修改如下:
"scripts": {
  // 将原来的 "serve":"vue-cli-service serve"改成如下的内容
  // 在运行项目的时候直接使用npm run dev即可(之前使用npm run serve)
  "dev": "vue-cli-service serve --mode dev",
  // 如果需要打包,一般都是需要发布到线上,这里我们还可以添加一条命令
  // 实现使用线上环境配置文件打包
  // "build-prod": "vue-cli-service build --mode prod"
  "build": "vue-cli-service build",
  "lint": "vue-cli-service lint"
}

配置代理请求

在开发环境的时候,前端人员和后端人员在同一个网络环境下用自己的电脑调试代码,就会存在跨域的问题,此时可以使用devServer来实现代理,如果这里理解不了,就可以直接认为和nginx代理相同。

devServer配置是在vue.config.js文件中,直接配置在modules.export = defineConfig({})下即可,内容格式如下:

modules.export = defineConfig({
  // devServer配置
  devServer: {
    proxy: {
      "/api": {
        // 代理到目标服务地址,api不用重复加
        target: "http://www.baidu.com/",
        // 表示支持跨域
        changeOrigin: true,
      },
    },
  },
});

注意点:devServer 代理在发送请求后,在浏览器控制台查看,请求地址不会改变成 target 指定的地址,但是实际已经代理完成。如果代理失败,vscode 的日志会给出提示。(也就是说不能根据请求地址来判断是否代理成功,要结合前后端控制台日志来判断)

本地缓存处理方案

本地缓存处理方案主要分为两种,一个是临时存储,当页面刷新的时候会出现丢失,一个就是永久存储,如果不去人为的清理和删除,将会一直保存下去。在 Vue 里面主要使用的全局状态管理的vuex和本地存储localStorage。对于localStorage可以进行一个标准化的封装,方便以后都可以使用。封装代码如下:(可以直接复制使用)

// 在src/utils目录里面创建一个js文件,进行封装操作

// 添加缓存
export const setItem = (key, value) => {
  // 存储基本数据:直接存储
  //存储复杂数据(如对象数据):先转成JSON字符串,再保存
  if (typeof value === "object") {
    value = JSON.stringify(value);
  }
  window.localStorage.setItem(key, value);
};

// 查询缓存
export const getItem = (key) => {
  // 同样分为基本数据类型和复杂数据类型
  // 先默认按照复杂数据类型处理,出现异常说明是简单数据类型,巧妙利用try、catch
  const result = window.localStorage.getItem(key);
  try {
    return JSON.parse(result);
  } catch (e) {
    return result;
  }
};

// 删除缓存
export const removeItem = (key) => {
  window.localStorage.removeItem(key);
};

// 清除所有缓存
export const cleanItem = () => {
  window.localStorage.clear();
};

公共的样式和颜色

在整个项目中,总是有一定的主色调,比如文字的主色调,背景的主色调,还有就是整个项目经常使用到的颜色,这个时候可以通过统一的抽取管理,放到variable.scss文件中,还有就是常用的样式,比如文字超过盒子的宽度后,需要使用省略号代替等,这样的通用样式通常是放到mixin.scss文件中。在组件中使用这些通用颜色和样式时,直接导入即可。

通用颜色用$加上颜色的名称,通用样式用@include加上样式定义时的名称,两种文件内定义的方式如下:

  • 通用颜色
// variable.scss定义通用颜色方式
$mainbackgroudColor: #f1f1f1
$mainFontColor: #333

// 如果颜色需要通过配合js动态变化,还可以将这些颜色导出出去,同时也方便使用
// 在scripts标签内,通过import variable from '@/style/variable.scss'即可
// 通过variable.mainbackgroudColor使用,在template模版中也是一样可以使用的
:export {
  mainbackgroudColor:$mainbackgroudColor
  mainFontColor:$mainFontColor
}

注意点:上面的样式文件,为了提高复用性和使用的方便性,避免在每个使用到公共颜色的组件中导入variable.js,可以将variable.js,导入到 store 中,通过 storegetters 将其提供给所有组件使用。编码方式如下:

import variable from "@/style/variable.js";
import { createStore } from "vuex";

export default createStore({
  state: () => ({}),
  getters: {
    variable: (state) => variable,
  },
});

// 在标签和style标签内使用:$store.getters.variable.bgColor
// 在js中使用:store.getters.variable.bgColor
  • 通用样式
// mixin.scss定义通用样式的方式
// 在组件中通过import '@/style/mixin.scss';即可
// 在样式中,直接使用 @include ellipsis;即可
@mixin ellipsis {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

使用 element-ui 调整样式的坑

在使用 element-ui 的时候,如果涉及修改组件的内部样式,需要使用v-deep来指定要是,否则会不生效。如el-input,其实际渲染后,会在外面包裹一层el-input__wrapper,这个时候如果需要修改el-input__wrapper样式,直接通过 class 名来修改是不能生效的,需要使用v-deep包裹你修改的样式。其中包裹分为以下两种情况。

/** 这种包裹只能实现单个样式,如果涉及多个样式就没有用了,
比如.el-input__wrapper,下的子标签,还有一个.el-input__xxx,
此时.el-input__xxx就不能生效啦。*/
::v-deep .el-input__wrapper {
  /** 样式内容*/
}
/** 为了防止上面的问题,直接一步到位 */
::v-deep() {
  .el-input__wrapper {
    /** 样式内容*/
  }
  input {
    /** 样式内容*/
  }
}

svgIcon 的使用和封装

svg 图标在项目中使用的很频繁,在使用 svg 图标的时候,可以使用 element-ui 自带的,也可以用外部的 svg 图标地址,另外还可以使用下载导入到项目本地的内部 svg 图标,其中 element-ui 的图标使用很简单,不在自定义 svgIcon 的考虑范围。那针对外部和内部的 svg 图标的封装就成为了关键。主要是为了方便使用,可以通过定义组件,在全局应用的方式来做。分为以下几步:

  1. 首先在 component 目录里面创建一个 svgIcon 目录,并创建 index.vue 文件,旨在创建 svg 图标的公共组件。代码如下:
<template>
  <div
    v-if="isExternal"
    :style="styleExternalIcon"
    class="svg-external-icon svg-icon"
    :class="className"
  />
  <svg v-else class="svg-icon" :class="className" aria-hidden="true">
    <use :xlink:href="iconName" />
  </svg>
</template>

<script setup>
import { defineProps, computed } from "vue";

/**
 * 判断是否为外部资源,当以https、mailto、tel开头的时候就认为是外部资源
 */
const external = (path) => {
  return /^(https?:|mailto:|tel:)/.test(path);
};

const props = defineProps({
  // icon 图标
  icon: {
    type: String,
    required: true,
  },
  // 图标类名
  className: {
    type: String,
    default: "",
  },
});

/**
 * 判断是否为外部图标
 */
const isExternal = computed(() => external(props.icon));
/**
 * 外部图标样式
 */
const styleExternalIcon = computed(() => ({
  mask: `url(${props.icon}) no-repeat 50% 50%`,
  "-webkit-mask": `url(${props.icon}) no-repeat 50% 50%`,
}));
/**
 * 项目内图标
 */
const iconName = computed(() => `#icon-${props.icon}`);
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}

.svg-external-icon {
  background-color: currentColor;
  mask-size: cover !important;
  display: inline-block;
}
</style>
  1. 上面的代码主要可以处理外部 svg 图标,但是内部 svg 图标就需要手动的导入。在 src 下创建 icons 目录,并创建 index.js 文件,代码如下:
import SvgIcon from "@/components/svgIcon";

// https://webpack.docschina.org/guides/dependency-management/#requirecontext
// 通过 require.context() 函数来创建自己的 context
const svgRequire = require.context("./svg", false, /\.svg$/);
// 此时返回一个 require 的函数,可以接受一个 request 的参数,用于 require 的导入。
// 该函数提供了三个属性,可以通过 require.keys() 获取到所有的 svg 图标
// 遍历图标,把图标作为 request 传入到 require 导入函数中,完成本地 svg 图标的导入
svgRequire.keys().forEach((svgIcon) => svgRequire(svgIcon));

export default (app) => {
  app.component("svg-icon", SvgIcon);
};
  1. 在上一步的 src/icons 目录下创建一个 svg 目录,将在网上下载好的 svg 图标文件放入,为这里非要这么放,看上面的代码,require.context 方法里面指定的 svg 目录。(这个可以根据自己的实际需求修改和自定义的)

  2. main.js 文件中配置上面的 icons。代码如下:

import { createApp } from "vue";
import App from "./App.vue";
import installIcons from "./icons";
import router from "./router";
import store from "./store";
import ElementPlus from "element-plus";

const app = createApp(App);
installIcons(app);
app.use(store).use(router).use(ElementPlus).mount("#app");
  1. 到这里基本是结束了,但是在页面上还是不能展示 svg 图标,原因是需要安装svg-sprite-loader组件。安装命令如下:
npm i svg-sprite-loader --save-dev
  1. 配置上一步安装的组件,配置内容写在 vue.config.js 文件中,代码如下:
const { defineConfig } = require("@vue/cli-service");
const path = require("path");

function resolve(dir) {
  return path.join(__dirname, dir);
}

module.exports = defineConfig({
  transpileDependencies: true,
  // 配置svg-sprite-loader
  chainWebpack(config) {
    config.module.rule("svg").exclude.add(resolve("src/icons")).end();
    config.module
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
      })
      .end();
  },
});
  1. 到这就配置结束,可以正常使用了,那该如何使用呢,看下面的实例代码。
<!-- 为什么标签名是svg-icon,可以注意看第二步的最后一行代码,导出的组件名称就是svg-icon -->
<!-- icon指定svg图标的名称或外部链接地址,传入到svg-icon组件中,这里不是连接地址,那就是使用的内部svg图标 -->
<svg-icon :icon="user"></svg-icon>

注意点:这个代码是完整的 svgIcon 的封装,可以直接使用,主要是本地 svg 图标需要自己去下载,另外就是 svg-icon 中 icon 属性如果指定的是内部 svg 图标,则这个名称和 svg 文件的名称是对应的。

store 的封装和使用

在项目内容变多,模块变多的时候,关于全局操作和变量也会同步的变多,这里就需要对 store 进行分离,根据模块的不同,封装参数和操作方法。常规的封装步骤如下:

  • 第一步:在 store 下创建一个名为 modules 的目录,对各个分模块创建对应的 js 文件,文件内容格式如下:
// export导出
export default {
  // 这里的namespaced必须要加
  namespaced: true,
  state: () => ({
    sidebarOpened: false,
  }),
  // mutations中的方法,接收的第一个参数是state
  // 相对比于actions中的方法,actions内的方法接收的是store
  // 调用mutations中的方法使用commit
  // 调用actions中的方法使用dispatch
  // 如果需要写异步方法,只能写在actions中,不能写在mutations中
  mutations: {
    triggerSidebarOpened(state) {
      state.sidebarOpened = !state.sidebarOpened;
    },
  },
  actions: {},
};
  • 第二步:在 store 目录下的 index.js 文件中导入这个模块
import { createStore } from "vuex";
// 导入对应的模块
import app from "./modules/app";
// 导入getters
import getters from "./getters";

export default createStore({
  // 引入getters
  getters,
  modules: {
    // 在modules中引入模块
    app,
  },
});
  • 第三步:封装 getters,方便在代码中获取属性,封装完成后
// 定义getters
const getters = {
  // 这里通过state获取数据的时候,需要指定模块名称,如:app、user等
  // 从user模块获取token
  token: (state) => state.user.token,
  // 从app模块获取sidebarOpened
  sidebarOpened: (state) => state.app.sidebarOpened,
};
// 导出getters
export default getters;
  • 第四步:将 getters 导入到 index.js 文件中,见第二步的代码

  • 第五步:使用 getters 内的属性和使用各个模块中定义的方法

<template>
  <!-- 在标签中获取store中的state数据,
  需要使用$store.getters.token,
  相对于js中获取值,多一个$符号 -->
  <div id="$store.getters.token"></div>
</template>
<script>
  import store from "@/store";

  const testMethod = () => {
    // 在js中获取store中state数据,使用store.getters.token即可
    console.log(store.getters.token);
    // 这里调用方法一定要注意,需要在方法名前添加上对应的模块名
    store.commit("app/triggerSidebarOpened");
  };
</script>

注意点:

  1. 使用 store 内的 state 定义的属性时,可以统一直接用 store.getters.xxx 获取。
  2. 调用方法的时候(以 mutations 内的方法为例),一定一定一定要在方法名前添加模块名,在没有分模块的时候,直接使用 commit('triggerSidebarOpened')调用方法,在分了模块后,需要先指定模块,再调用方法,改写成 commit("app/triggerSidebarOpened")

route.matched 面包屑的数据获取

在后台管理系统中,面包屑是必备的内容,如何获取目录的层级关系,并展示,vue-router 中提供了 route.matched 方法,可以获取当前目录及其父目录的信息,最终以数组的方式返回。注意本身的路由表数据需要满足父子关系,否则无法采用这种方式实现

screenfull 全屏展示组件

就是浏览器全屏的功能。实现步骤如下:

  1. 安装 screenfull 组件,执行npm i screenfull --save-dev;

  2. 指定触发 screenfull 的事件,代码如下:

import screenfull from "screenfull";

const toggleScreenfull = () => {
  // 这是screenfull组件自带的方法
  screenfull.toggle();
};
  1. 到这里就结束了,但是一般页面提供点击全屏,再点击退出全屏的按钮都是会随着状态发生变化的。随意这里还要添加几行代码。
import { onMounted, onUnmounted } from "vue";

const isFullScreen = ref(false);

// 修改isFullScreen值,控制按钮的状态变化
const screenfullChange = () => {
  // screenfull自带有一个isFullscreen属性
  isFullScreen.value = screenfull.isFullscreen;
};

/**
 * screenfull还提供了另外两个方法,就是监听
 * off:表示结束监听
 * on:表示绑定监听
 */
onMounted(() => {
  // 监听change事件并调用 screenfullChange 方法
  screenfull.on("change", screenfullChange);
});
onUnmounted(() => {
  // 监听change事件并调用 screenfullChange 方法
  screenfull.off("change", screenfullChange);
});

搜索插件 fuse 使用

前端简易的关键词搜过功能,针对小体量的数据很有用,响应速度快。实现方式采用以下步骤。

  1. 首先安装fuse.js,使用npm i fuse.js --save;

  2. 在代码中使用,Fuse 对象来实现筛选功能,代码如下:

// 首先引入fuse.js
import Fuse from 'fuse.js';
// 创建fuse对象,dataList对应需要检索的数据列表
const fuse = new Fuse(dataList,{
  // 是否按照优先级排序
  shouldSort: true;
  // 匹配长度达到对少时,认为被匹配上
  minMatchCharLength:1,
  // 设置每个字段的搜索权限
  keys: [
    {
      name:'title',
      weight: 0.7
    },
    {
      name:'path',
      weight:0.3
    }
  ]
});
// 使用fuse,将关键字传入搜索,返回对应的结果,当没有匹配到的时候,返回的是一个空数组
const result = fuse.search(keyword);

注意:这里的数据结构是有要求的,需要和指定 keys 中的字段对应,实例如下:

// 这里title和name都可以是数组,比如标题可以有主标题和副标题两个,最为数组的两个元素
[
  {
    "title": "这是标题一",
    "path": "这是路径一"
  },
  {
    "title": "这是标题二",
    "path": "这是路径二"
  }
]

说明:这种搜索一般都是配合 select 选择器使用,效果很好。

全局日期处理工具 day.js

在定义 svgIcon 的时候就是使用全局定义,日期处理插件可能在整个项目都会使用到,和 svgIcon 一样,定义一个全局日期处理工具,是个不错的选择。

  1. 安装day.js插件,执行npm i dayjs

  2. 定义个 filters 工具类,编写对应的处理代码;

import dayjs from "dayjs";

const dateFilter = (val, format = "YYYY-MM-DD") => {
  // 判断是不是数值类型,不是则进行转换
  if (!isNaN(val)) {
    val = parseInt(val);
  }
  return dayjs(val).format(format);
};

export default (app) => {
  app.config.globalProperties.$filters = { dateFilter };
};
  1. main.js中导入封装后的日期全局工具;
// 导入工具
import installFilters from "./filters";

const app = createApp(App);
// 安装到全局
installFilters(app);
  1. 工具的使用

在 template 中使用工具

<!-- 伪代码,dateTime是需要转换的日期 -->
<div>{{ $filters.dateFilter(dateTime) }}</div>
/**在js中调用 */
const { getCurrentInstance } from 'vue';
// 获取当前对象,就相当于vue2中的this
// 在vue3中没有this,通过这种方式获取
const {instance} = getCurrentInstance();
// 调用方法
const dateTime = instance.$filters.dateFilter(timestamp);

通过这种方式,其实还可以自己定义日期工具类,使用new Date()的相关 API 实现。

打包发布

打包发布关键问题就是 nginx 的配置,nginx 配置分为两种情况,一种是直接location / {},一种是location /xxx {}。分别配置如下:

  • 以根目录配置 location
server {
  listen 8080;
  server_name localhost;
  # 目录浏览
  autoindex on;
  # 缓存处理
  add_header Cache-Control 'no-cache, must-revalidate';
  location / {
    root /usr/local/nginx/dist/;
    # 跨域
    add_header Access-Control-Allow-Origin *;
    # 返回index.html
    try_files $uri $uri/ /index.html;
  }
}
  • 以非根目录配置 location
location /xxx {
  alias /usr/local/nginx/xxx;
  # 跨域
  add_header Access-Control-Allow-Origin *;
  try_files $uri $uri/ /xxx/index.html;
}

注意:在打包之前,需要配置一下项目的访问路径,正常不配置路径时,对应的根目录配置。

数字展示动态效果 countup.js

数字展示动态效果表示在页面加载的时候,数字不是直接展示,而是从 0 开始快速递增到实际值的过程。

  1. 安装countup.js,通过npm i countup.js --save命令安装;
  2. 在使用的地方导入,使用import { CountUp } from 'countup.js'导入;
  3. 创建CountUp并使用。
// CountUp的使用需要三个参数,分别是当前的dom对象,实际值,操作项
import { CountUp } from 'countup.js';
import { ref,onMounted } from 'vue';

const targetDom = ref(null);
onMounted(()=>{
  // 配置操作项
  const options = {
    // 小数点位,这里表示数值的小数位数
    decimalPlaces:2,
    // 动态效果时间,单位秒
    duration: 1.3
  },
  // 创建完成后,通过start方法启动
  new CountUp(targetDom, 2345.12,options).start();
})

Echart.js 使用步骤

Echart.js 使用步骤基本都是相同的,唯一不同的 Echart 内容是不同的。实现步骤如下:

  1. 安装 Echart.js,执行npm i echart.js --save命令;
  2. 代码体现如下
<template>
  <!-- 2. 创建一个标签,用来放echart图表 -->
  <div ref="echartContainer"></div>
</template>
// 1. 导入echart
import * as echarts from "echart.js";
import { ref, onMounted } from "vue";
// 2. 创建一个标签,用来放echart图表
const echartContainer = ref(null);
// 3. 页面加载的时候构建图表,通过echarts.init方法
let myEchart;
onMounted(() => {
  myEchart = echarts.init(echartContainer.value);
  renderChart();
});

const renderChart = () => {
  // 4. 构建options 配置对象(echart 渲染的核心)
  // 这里可以参考官方的文档:https://echarts.apache.org/zh/option.html#title
  const options = {
    // 标题
    title: {
      text: "标题",
    },
    // 图例
    legend: {
      // 是个数组,表示可以添加多个图例,并设置对应的样式、icon等
      data: [
        {
          name: "图例1",
          icon: "图例图标",
          textStyle: {
            color: "red",
          },
        },
      ],
    },
    // 表示X轴的值
    xAxis: {
      data: ["A", "B", "C", "D"],
    },
    // 表示Y轴的配置
    yAxis: {},
    // 图表类型,比如柱状图,折线图等,可以设置多个
    series: [
      {
        name: "销量",
        type: "bar", //图表的类型
        data: [100, 200, 50, 120], // 图表的数据
      },
    ],
  };
  // 5. 将options加入到当前图表中
  myEchart.setOption(options);
};

组件间的通信 mitt.js

在 vue 中,组件间通信方式不止于一种,各有不同,总结下来主要是以下几种:

父子组件间的通信

这个交互方式相对于比较简单,最最常用的就是propsemits来完成。

<!-- 父组件伪代码 -->
<!-- 传递id到子组件,定义一个事件,可由子组件触发,调用父组件方法,对数据进行操作 -->
<template>
  <childVue :id="id" @updateUserId="updateUser"></childVue>
</template>
<script setup>
  const id = "1234";
  const updateUser = () => {
    console.log("user ID update success");
  };
</script>
<template>
  <div @click="changeId">{{ id }}</div>
</teplate>
<script setup>
import { defineProps,defineEmits } from 'vue';

// 接收父组件的数据
defineProps ({
  id: {
    required:true,
    type: String
  }
});
// 接收父组件的事件
const emits = defineEmits:['updateUserId']
const changeId = ()=> {
  emits('updateUserId','4321');
}
</script>

非父子组件间的通信

这种情况相对于父子组件间通信较为复杂,主要有以下三种方式。

  1. 找到双方的共同父组件: 通过子向父,父向子通讯的方式,将父组件作为通信的中转来实现。

优点:以现有的技术即可实现
缺点:大量的跨组件处理事件,会导致逻辑变得非常的复杂,各组件之间的耦合性变强

  1. 借助 vuex:因为 vuex 是可以与所有组件都进行交互的,所以可以借助它承担沟通者的角色

优点:以现有的技术即可实现
缺点:数据必须由 vuex 处理,如果数据之前没有存放在 vuex 中,则需要进行较大的改动

  1. 事件中心EventHb:所谓的事件中心其实就是一个公开的单例,一般会提供**emits发送事件on监听事件两个方法。在 Vue2 中vm实例因为直接具备这两个方法,所以可以直接作为事件中心实例。但是在 Vue3 中,取消了on方法, 所以如果想要实现这种机制,则需要通过mitt**来实现。

优点:有利于组件之间的解耦合,增加可维护性
缺点:当事件足够多时,会导致事件中心极其复杂

总结:从上面的分析,如果是父子组件间的通信,直接使用propsemits是最优的选择,但在非父子组件间的通信情况下,考虑到代码的可维护性、组件间的耦合性、代码的复杂度,综合下来采用事件中心EventHub是最优的选择,特别是在 Vue3 中。

mitt.js 的使用

  1. 安装 mitt.js(git 上的 mitt,具体可以参考 mitt 文档)。
npm i mitt@3.0.0 --save
  1. 使用 mitt.js,在src/utils目录下创建一个eventHub.js文件。
import mitt from "mitt";

//const emitter = mitt();
//export default mitter;

// 这一句代替上面两句
export default mitt();
  1. 在需要使用事件中心的组件中导入src/utils/eventHub.js

  2. 假设 A 组件中的某个属性发生改变,触发事件中心进行修改。

import watch from "vue";
import emitter from "@/utils/eventHub.js";

// 监听日期的变化,并将日期作为事件触发
watch(currentDate, (val) => {
  console.log("currentDateChange emit", val);
  emitter.emit("currentDateChange", val);
});
  1. 假设 B 组件需要 A 实时获取 A 组件中的此属性变化,并做相应的操作。
import emitter from "@/utils/eventHub.js";

// 监听时间的变化,并做数据处理操作
emitter.on("currentDateChange", (val) => {
  console.log("currentDateChange on", val);
  // 一系列数据操作
});

文档云 wordcloud.js

文档云图主要的特性:根据给定的轮廓绘制、云图内容颜色各有不同。
颜色的不同可以通过随机生成颜色的方式来实现。轮廓的绘制,可以提前准备一个png图片,wordcloud 会自动在非透明区域绘制云图内容。

  1. 安装 wordcloud.js(github 上的 wordcloud,具体操作可参考)。
npm install echarts-wordcloud@2.1.0 --save
  1. 使用 wordcloud 实现文档云
<template>
  <div ref="wordcloudRef"></div>
</template>
<script setup>
  import * as echarts from "echarts";
  import "echarts-wordcloud";
  import { onMounted } from "vue";
  import wordCloudBg from "@/assets/wordcloud.png";
  import { randomRGB } from '@/utils/color'

  const wordCloudData = ref([]);
  // 通过请求接口获取文档云数据
  const getWordCloudData = async () => {
    const res = await getWordCloudData();
    wordCloudData.value = res;
    // 构建option和加载文档云
    renderChart();
  };

  // 调用方法
  getWordCloudData();

  const wordcloudRef = ref(null);
  let mChart;
  onMounted(() => {
    mChart = echarts.init(wordcloudRef.value);
  });

  // 图像轮廓
  var maskImage = new Image();
  maskImage.src = wordCloudBg;

  const renderChart = () => {
    var option = {
      title: {
        text: "文档云案例",
      },
      series: [
        {
          // 类型
          type: "wordcloud",
          // 可以通过left、top、right、bottom、width、height来指定x轴偏移量,y轴偏移量,canvas宽度,canvas高度
          // 文字大小的范围
          sizeRange: [4, 80],
          // 角度范围
          rotationRange: [0, 0],
          // 单词间距
          gridSize: 0,
          // drawOutBound: false,是否允许词太大的时候,超出画布范围
          // 展示过渡效果
          layoutAnimation: true,
          // 指定轮廓图像,也可以通过shape属性指定绘制形状,如:circle
          maskImage: maskImage;
          textStyle: {
            // 可以完全随机颜色,也可以根据指定的几种颜色随机,根据实际逻辑封装
            color: randomRGB(),
          },
          // 高亮效果,当鼠标悬停时的效果
          emphasis: {
            focus: 'self',
            textStyle: {
              fontWeight: "blod",
              color: "#000",
            },
          },
          // 指定文档云数据
          data: wordCloudData.value,
        },
      ],
    };

    // 需要等待图像加载结束,再加载文档云数据
    maskImage.onload = function() {
      mChart.setOption(option);
      mChart.on('click',params=>{
        // 对应当前文字的数据,可以实现跳转等动作
        console.log(params);
      });
    }
  };
</script>

注意:这里的文档云的数据格式要求。

const data = [
  {
    name: "item name",
    value: 123,
  },
];

必须要有namevalue两个值。name表示在文档云中展示的名称,名称显示的大小需要根据value值进行计算。

  1. 随机生成颜色工具类,在src/utils目录下创建color.js文件。
export const randomRGB = () => {
  const r = Math.floor(Math.random() * 255);
  const g = Math.floor(Math.random() * 255);
  const b = Math.floor(Math.random() * 255);
  return `rgb(${r},${g},${b})`;
};

百度地图

地图可视化,可以通过百度地图申请 API 和EChartsbmap.js模块结合完成。

  1. public/index.html中,导入百度地图 API(需要到百度地图 API 官网申请权限,申请地址百度地图 API,有账号直接登陆,无账号先申请账号,操作结束登录后,进入控制台,提示注册开发者,根据要求一步一步完成申请即可。申请开发者完成后,再次进入控制台,进入应用下的我的应用,创建应用,就能获取一个访问应用的 AK,然后完成百度地图 API 在项目中的导入,具体教程可看官方文档:账号和获取密钥
<script src="https://api.map.baidu.com/api?v=3.0&ak=${djOGmYOoPif123445D8kWvtUIFFNKGU0}"></script>
  1. 引入 bmap 的拓展模块,实现地图加载。
<template>
  <div ref="bmapRef"></div>
</template>
<script setup>
  import * as echarts from "echarts";
  import "echarts/extension/bmap/bmap.js";
  import { ref, onMounted } from "vue";

  const bmapRef = ref(null);
  let mChart = null;
  onMounted(() => {
    mChart = echarts.init(bmapRef.value);
  });
  const mapData = [
          [109.114129, 36.550339, 120],
          [103.114129, 35.550339, 60],
        ];
  const renderMap = () => {
    const options = {
      tooltip: {
        trigger: "item",
      },
      // 地图配置
      bmap: {
        // 地图的中心点
        center: [109.114129, 36.550339],
        // 缩放级别(初始值)
        zoom: 5,
        // 是否可拖动
        roam: true,
      },
      series: [
        {
          name: "百度地图案例",
          // 撒点类型:散点图
          type: "scatter",
          // 使用的表坐标
          coordinateSystem: "bmap",
          // 数据源
          data: mapData,
          // 散点大小,val表示当前点的数据
          symbolSize: function (val) {
            return val[2] / 10;
          },
          // 数据使用下标为2的值,下标为0,1的值是经纬度
          encode: {
            value: 2
          }
          // 鼠标移入点时高亮样式
          emphasis: {
            label: {
              show: true
            }
          },
          // 散点色值
          color: '#15803D',
        },
        {
          name: '百度地图案例,数值top5动态效果',
          type: 'effectScatter',
          coordinateSystem: 'bmap',
          // 先排序,再取前五条数据
          data: mapData.sort(function(a,b){
            return b.value - a.value;
          }).slice(0,6),
          symbolSize: function(val)=>{
            return val[2] /10;
          },
          encode: {
            value: 2
          },
          // 涟漪特效
          rippleEffect: {
            brushType: 'stroke'
          },
          label: {
            formatter:'{b}',
            position: 'right',
            show: true
          },
          // 散点层级
          zlevel: 2,
          // 散点色值
          color: '#166534'
        }
      ]
    };
    mChart.setOption(options);
  };
</script>

注意:bmap 对数据的格式有一定的要求:

const bmapData = [
  [109.114129, 36.550339, 120],
  [109.114129, 36.550339, 120],
  [109.114129, 36.550339, 120],
];

其中前两个参数分别表示经度和纬度,第三个参数才是对应的数据。


文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
 上一篇
CSS3知识学习笔录 CSS3知识学习笔录
都说CSS3很简单,但是为啥我觉得很难呢,特别是样式遇到问题时,总是没有头绪,真的是搞事情,必须好好学习一下。
2023-04-08
下一篇 
Mac电脑上安装Homebrew工具 Mac电脑上安装Homebrew工具
你有没有遇到在mac中安装各种软件很费劲,比如安装nginx,就会因为依赖包的问题各种折磨你,但是使用Homebrew一键安装即可。随之而来的就是Homebrew安装问题,下面来一起讨论讨论……
2023-03-30
  目录