normalize.css 文件的安装
normalize.css 的作用是在项目编译打包的时候磨平各浏览器对 css 的不同展示带来的差异。
安装命令npm install mormalize.css --save
。
安装完成后,在main.js
文件中导入。
import "normalize.css";
eslint 和 Prettier 实现代码统一规范
在管理前端代码过程中,或者在开发过程中,总是会有不符合规范的编码,如双引号和单引号的混合使用,没有格式化代码,没有层次感等等。这个时候 vue 中的一些插件给我们很好的解决了,目前了解的最好的方案就是eslint + Prettier
实现。
配置过程:
- 首先需要在
vscode
上安装Prettier
插件,直接在vscode
插件市场里面找到安装即可。 - 项目本身需要使用
eslint
,如果在搭建脚手架的时候忘记安装eslint
,可以通过npm
安装,不做赘述。 - 在项目目录下,也就是
vue.config.js
文件所在的同级目录下,创建一个.prettierrc
文件。 .prettierrc
文件中内容如下:
{
"semi": true,
"trailingComma": "none",
"singleQuote": true
}
semi:
表示声明代码结尾使用分号,在 JAVA 语言中,都是要用分号结尾,表示一行代码或者代码块结束,在 js 中,可以不用分号,这是使用true
,也可以不使用。看个人习惯,确定是否添加(作为 Java 程序猿,必须要加一下,强迫症);singleQuote:
指定单引号,在 js 中字符串使用单引号和双引号都是可以的,但是一般都是使用单引号,因此这里设置此属性为true
;trailingComma:
多行使用拖尾逗号,也就是说在定义多个属性或者方法的时候,最终结束需要使用逗号,最后一个方法或者属性不加逗号,这里使用 none,表示不加.
.prettierrc
文件还有很多其他的配置,可以自行添加,不做赘述。
配置 vscode,实现保存文件的时候,自动按照配置的规则,对代码不符合要求的部分进行校验的修改。
- 点击 vscode 的左下角设置,进入设置页面;
- 在设置页面搜索
save
; - 找到
Editor:Fromat On Save
,将其选中。
通过以上配置即可使用啦!
注意问题:
Prettier
和eslint
存在冲突,Prettier
认为方法名和后面的括号应该放在一起,中间没有空格,但是 eslint 觉得方法和括号之间需要有空格,此时会报错,这个可以在.eslintrc.js
文件中增加以下配置。(但是在高版本里面这个冲突问题好像已经不存在了)
'space-before-function-paren': 'off'
制表符在
Prettier
中是 2 个字符,而在 Vscode 默认是 4 个,需要在设置里面,将制表符修改成 2 个字符。找到Edit:Tab size
选项,将其值改为 2。如果存在多个代码格式化工具,要么卸载,要么通过右击文件内容区域,选择格式化文档将
Prettier
设置为默认即可。
推荐配置:
- 在
.eslintrc.js
文件的rules
中添加一下'no-unused-vars': 'off'
,用于提示没有使用的变量或者导入的依赖没有用时候,都会报错,可以考虑关闭,错误提示会少一点,要是希望代码看起来更优雅,可以考虑开发完对代码进行优化时使用,开发过程中,还是建议关闭,有点烦。
axios 服务调用封装
使用步骤:
- 在使用前,需要通过
npm
命令安装axios
工具。(npm install axios --save
) - 在实际使用中,需要在
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
提供了三套环境,分别是development
、test
、production
。创建文件格式是.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";
注意和优化点:
- 是需要注意的配置文件中,除了标识文件类型使用
ENV
或者NODE_ENV
,其他配置项的名称都要以VUE_APP_
开头,否则不会被识别。 - 我们正常的执行
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
中,通过 store
的 getters
将其提供给所有组件使用。编码方式如下:
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 图标的封装就成为了关键。主要是为了方便使用,可以通过定义组件,在全局应用的方式来做。分为以下几步:
- 首先在
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>
- 上面的代码主要可以处理外部 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);
};
在上一步的
src/icons
目录下创建一个svg
目录,将在网上下载好的 svg 图标文件放入,为这里非要这么放,看上面的代码,require.context
方法里面指定的 svg 目录。(这个可以根据自己的实际需求修改和自定义的)在
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");
- 到这里基本是结束了,但是在页面上还是不能展示 svg 图标,原因是需要安装
svg-sprite-loader
组件。安装命令如下:
npm i svg-sprite-loader --save-dev
- 配置上一步安装的组件,配置内容写在 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();
},
});
- 到这就配置结束,可以正常使用了,那该如何使用呢,看下面的实例代码。
<!-- 为什么标签名是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>
注意点:
- 使用
store
内的state
定义的属性时,可以统一直接用store.getters.xxx
获取。 - 调用方法的时候(以
mutations
内的方法为例),一定一定一定要在方法名前添加模块名,在没有分模块的时候,直接使用commit('triggerSidebarOpened')
调用方法,在分了模块后,需要先指定模块,再调用方法,改写成commit("app/triggerSidebarOpened")
。
route.matched 面包屑的数据获取
在后台管理系统中,面包屑是必备的内容,如何获取目录的层级关系,并展示,vue-router
中提供了 route.matched
方法,可以获取当前目录及其父目录的信息,最终以数组的方式返回。注意本身的路由表数据需要满足父子关系,否则无法采用这种方式实现。
screenfull 全屏展示组件
就是浏览器全屏的功能。实现步骤如下:
安装 screenfull 组件,执行
npm i screenfull --save-dev
;指定触发 screenfull 的事件,代码如下:
import screenfull from "screenfull";
const toggleScreenfull = () => {
// 这是screenfull组件自带的方法
screenfull.toggle();
};
- 到这里就结束了,但是一般页面提供点击全屏,再点击退出全屏的按钮都是会随着状态发生变化的。随意这里还要添加几行代码。
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 使用
前端简易的关键词搜过功能,针对小体量的数据很有用,响应速度快。实现方式采用以下步骤。
首先安装
fuse.js
,使用npm i fuse.js --save
;在代码中使用,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 一样,定义一个全局日期处理工具,是个不错的选择。
安装
day.js
插件,执行npm i dayjs
;定义个 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 };
};
- 在
main.js
中导入封装后的日期全局工具;
// 导入工具
import installFilters from "./filters";
const app = createApp(App);
// 安装到全局
installFilters(app);
- 工具的使用
在 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 开始快速递增到实际值的过程。
- 安装
countup.js
,通过npm i countup.js --save
命令安装; - 在使用的地方导入,使用
import { CountUp } from 'countup.js'
导入; - 创建
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 内容是不同的。实现步骤如下:
- 安装 Echart.js,执行
npm i echart.js --save
命令; - 代码体现如下
<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 中,组件间通信方式不止于一种,各有不同,总结下来主要是以下几种:
父子组件间的通信
这个交互方式相对于比较简单,最最常用的就是props
和emits
来完成。
<!-- 父组件伪代码 -->
<!-- 传递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>
非父子组件间的通信
这种情况相对于父子组件间通信较为复杂,主要有以下三种方式。
- 找到双方的共同父组件: 通过子向父,父向子通讯的方式,将父组件作为通信的中转来实现。
优点:以现有的技术即可实现
缺点:大量的跨组件处理事件,会导致逻辑变得非常的复杂,各组件之间的耦合性变强
- 借助 vuex:因为 vuex 是可以与所有组件都进行交互的,所以可以借助它承担沟通者的角色
优点:以现有的技术即可实现
缺点:数据必须由 vuex 处理,如果数据之前没有存放在 vuex 中,则需要进行较大的改动
- 事件中心
EventHb
:所谓的事件中心其实就是一个公开的单例,一般会提供**emits
发送事件和on
监听事件两个方法。在 Vue2 中vm
实例因为直接具备这两个方法,所以可以直接作为事件中心实例。但是在 Vue3 中,取消了on
方法, 所以如果想要实现这种机制,则需要通过mitt
**来实现。
优点:有利于组件之间的解耦合,增加可维护性
缺点:当事件足够多时,会导致事件中心极其复杂
总结:从上面的分析,如果是父子组件间的通信,直接使用props
和emits
是最优的选择,但在非父子组件间的通信情况下,考虑到代码的可维护性、组件间的耦合性、代码的复杂度,综合下来采用事件中心EventHub
是最优的选择,特别是在 Vue3 中。
mitt.js 的使用
- 安装 mitt.js(git 上的 mitt,具体可以参考 mitt 文档)。
npm i mitt@3.0.0 --save
- 使用 mitt.js,在
src/utils
目录下创建一个eventHub.js
文件。
import mitt from "mitt";
//const emitter = mitt();
//export default mitter;
// 这一句代替上面两句
export default mitt();
在需要使用事件中心的组件中导入
src/utils/eventHub.js
。假设 A 组件中的某个属性发生改变,触发事件中心进行修改。
import watch from "vue";
import emitter from "@/utils/eventHub.js";
// 监听日期的变化,并将日期作为事件触发
watch(currentDate, (val) => {
console.log("currentDateChange emit", val);
emitter.emit("currentDateChange", val);
});
- 假设 B 组件需要 A 实时获取 A 组件中的此属性变化,并做相应的操作。
import emitter from "@/utils/eventHub.js";
// 监听时间的变化,并做数据处理操作
emitter.on("currentDateChange", (val) => {
console.log("currentDateChange on", val);
// 一系列数据操作
});
文档云 wordcloud.js
文档云图主要的特性:根据给定的轮廓绘制、云图内容颜色各有不同。
颜色的不同可以通过随机生成颜色的方式来实现。轮廓的绘制,可以提前准备一个png
图片,wordcloud 会自动在非透明区域绘制云图内容。
- 安装 wordcloud.js(github 上的 wordcloud,具体操作可参考)。
npm install echarts-wordcloud@2.1.0 --save
- 使用 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,
},
];
必须要有name
和value
两个值。name
表示在文档云中展示的名称,名称显示的大小需要根据value
值进行计算。
- 随机生成颜色工具类,在
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 和ECharts
的bmap.js
模块结合完成。
- 在
public/index.html
中,导入百度地图 API(需要到百度地图 API 官网申请权限,申请地址百度地图 API,有账号直接登陆,无账号先申请账号,操作结束登录后,进入控制台,提示注册开发者,根据要求一步一步完成申请即可。申请开发者完成后,再次进入控制台,进入应用下的我的应用,创建应用,就能获取一个访问应用的 AK,然后完成百度地图 API 在项目中的导入,具体教程可看官方文档:账号和获取密钥)
<script src="https://api.map.baidu.com/api?v=3.0&ak=${djOGmYOoPif123445D8kWvtUIFFNKGU0}"></script>
- 引入 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],
];
其中前两个参数分别表示经度和纬度,第三个参数才是对应的数据。