前端开发环境配置及Vue开发样例

6/7/2021 VSCodeVuenpm私有仓库Element-UIEchartsnativefier

# 1. 编辑器及基本开发环境

# 1.1 VSCode编辑器

# 1.1.1 简介及下载

VS Code 是一款由微软开发且跨平台的免费源代码编辑器。该软件支持语法高亮、代码自动补全、代码重构、查看定义功能,并且内置了命令行工具和Git版本控制系统。安装几个扩展插件后,就非常适合作为前端的开发工具了。

官网下载:https://code.visualstudio.com/ (opens new window)

VSCode界面

# 1.1.2 扩展插件

VS Code支持安装扩展以增强功能(设置——扩展——搜索安装扩展),常用的有以下几个:

# 1.1.3 喜好设置

主题选择:Monokai Dimmed 键映射:Eclipse Keymap

自动同步:通过Github账号 自动保存:onFocusChange

# 1.1.4 常见问题

[1] 打开终端和控制台

  • 快捷键:Ctrl + `(键盘左上方英文状态的点)

[2] VS Code设置不自动关闭文件

  • 打开VS Code设置,Tab选择用户,在搜索框输入:workbench.editor.enablePreview,对应的设置项取消勾选即可。

[3] 文件标签栏多行显示

  • 打开VS Code设置,Tab选择工作区,在搜索框输入:workbench.editor.wrapTabs,勾选该设置项即可。

[4] 在VS Code里预览svg

  • 安装SVG Gallery扩展,然后打开svg,右键选择Open in SVG Gallery

  • 另注:svg矢量图标资源库——iconfont (opens new window)

# 1.2 Node.js环境

# 1.2.1 安装Node.js

方式一:用nvm安装多个版本的Node.js(建议采用此方式,因为有些前端依赖需要的Node.js不兼容)

安装:nvm官网 (opens new window)(win端下载zip即可,安装过程会自动配置环境变量,之后打开管理员cmd输入nvm显示版本号即安装成功)

使用:使用nvm安装并管理多版本的node.js

[1] 安装多版本的node.js

$ nvm install v10.16.2 
$ nvm install v16.13.0
$ nvm list  // 显示所有已安装的node.js
1
2
3

[2] 切换使用某版本的node.js

$ nvm use v16.13.0
1

注意事项:

1)安装nvm之前,如果你已经安装了Node.js,建议先把它卸载掉(控制面板处卸载即可),以免当前环境产生干扰。

2)nvm use 报错exit status 145问题:nvm安装的时候有两个安装目录,一个是nvm安装目录,一个是nodejs安装目录,这两个安装目录名不能出现空格或中文,但是nodejs默认安装目录是 C:\Program Files\nodejs,这个目录中间有空格,需要我们自定义一个安装目录,即可解决这个问题。

3)nvm use 报错exit status 1问题:这里需要用管理员身份打开cmd命令行工具,再执行nvm use即可。

方式二:直接安装某版本的Node.js

安装:Node.js官网 (opens new window)(下载对应平台的.msi版本即可)

测试:打开cmd,输入node -vnpm -v,若显示node和npm的版本号即安装成功。

# 1.2.2 npm换源

由于npm 的源在国外,所以国内用户使用起来可能下载速度很慢,可换源解决此问题。

$ npm config set registry https://registry.npm.taobao.org  //配置淘宝镜像
$ npm config get registry //验证配置是否成功

// 清除设置的淘宝镜像
$ npm config delete registry
$ npm config delete disturl
1
2
3
4
5
6

注:其实我更推荐使用科学上网代理来解决,就不用配这些乱七八糟的镜像了。

# 1.2.3 npm安装yarn并换源

$ npm install -g yarn --registry=https://registry.npm.taobao.org
$ yarn config set registry https://registry.npm.taobao.org -g
$ yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g
1
2
3

# 1.2.4 npm及项目依赖问题

[1] npm install 安装出错问题

  • Step1:使用npm cache clean --force清除缓存。

  • Step2:打开C:\Users\xxx目录,删除node_modules文件夹中的对应包,package-lock.json文件。

    注:package-lock.json是在npm install安装时生成的一份文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号,如果没有这个文件的话,那么npm install将下载大版本下的最新的包。

  • Step3:重新执行npm install命令。

[2] npm install模块时报错not such file or directory,open ... package.json

是因为缺少 package.json 这个文件,初始化并重新安装依赖即可。

$ npm init -f
$ npm install formidable --save
1
2

[3] npm install 报错 gyp info it worked if it ends with ok npm ERR! gyp verb cli

先切换低版本node.js,亲测v10.16.2可以,而v16.13.0不行,然后执行如下命令安装:

$ npm install -g mirror-config-china --registry=http://registry.npm.taobao.org
$ npm install node-sass
$ npm install
1
2
3

[4] Node 18无法成功安装node-sass的依赖问题,报错信息如下:

npm ERR! ../src/libsass/src/ast.hpp:1616:14: note: use reference type 'const std::string &' to prevent copying
npm ERR!         for (const auto denominator : denominators)
npm ERR!              ^~~~~~~~~~~~~~~~~~~~~~~~
npm ERR!                         &
1
2
3
4

在 package.json 里删除 node-sass,然后更换为sass包即可

$ npm uninstall node-sass
$ npm install sass
$ npm install
1
2
3

[5] 解决 ERROR in Cannot find module 'node-sass' 报错

$ npm install --save-dev  --unsafe-perm node-sass
1

参数说明:--save-dev 用于让应用程序能够在本地 node_modules 中找到该模块,并将包添加到 package.json 中的 devDependencies。--unsafe--perm 用于以 root 身份运行安装脚本。

[6] 解决 Error: error:0308010C:digital envelope routines::unsupported 报错

$ export NODE_OPTIONS=--openssl-legacy-provider 
1

[7] 某些前端库需要依赖python2环境才能安装成功。

报错信息:Can't find Python executable "python", you can set the PYTHON env variable.

$ brew install pyenv 
$ pyenv install 2.7.18
$ npm config set python /Users/xxx/.pyenv/versions/2.7.18/bin/python
$ npm install
1
2
3
4

注:Mac Monterey默认移除了Python2。实际上Python官方宣布 2020 年 1 月后不再更新维护 Python2,最后版本为 2.7.18 (opens new window)。在2020年4月之前可使用 brew install python@2 来安装python2,但现在已经失效,使用 pyenv 可以成功安装 python2。

[8] 解决 ERESOLVE unable to resolve dependency tree 报错

$ npm install --legacy-peer-deps
1

# 1.2.5 搭建私有npm仓库

项目简介:verdaccio是一个简单的、零配置要求的本地私有 npm 注册表。无需整个数据库即可开始!Verdaccio 开箱即用, 拥有自己的小型数据库,并且能够代理其他注册表,并在此过程中缓存下载的模块。

项目地址:https://github.com/verdaccio/verdaccio (opens new window)

Step1:拉取镜像并创建容器挂载目录

$ docker pull verdaccio/verdaccio
$ docker run -itd --name verdaccio -p 4873:4873 verdaccio/verdaccio
$ mkdir -p /mydocker/verdaccio
$ docker cp verdaccio:/verdaccio/storage /mydocker/verdaccio/storage
$ docker cp verdaccio:/verdaccio/conf /mydocker/verdaccio/conf
$ docker cp verdaccio:/verdaccio/plugins /mydocker/verdaccio/plugins
$ docker rm -f verdaccio
$ docker run -itd --name verdaccio -p 4873:4873 -v /mydocker/verdaccio/storage:/verdaccio/storage -v /mydocker/verdaccio/conf:/verdaccio/conf -v /mydocker/verdaccio/plugins:/verdaccio/plugins verdaccio/verdaccio
$ docker update verdaccio --restart=always 
1
2
3
4
5
6
7
8
9

Chrome 浏览器打开 http://ip:4873,会看到如下管理界面。

verdaccio

Step2:发布npm包到私有库的流程

有了私有库以后,就可以在其上发布 npm 包。但初始化时需要先添加用户,设置用户名和密码等,然后就可以直接发包了。

添加用户:输入如下命令,然后输入用户名、密码和邮箱。

$ npm adduser --registry http://ip:4873/
1

发布npm包:当需要把某个项目发布到私有库时,直接publish。发布成功后,刷新页面,就能看到最新发布的包。

$ npm publish --registry http://ip:4873
1

Step3:安装私有库npm包的流程

在项目目录下增加 .npmrc 文件,指定仓库地址。使用 npm install 包名,即可安装私有包了。

registry=http://ip:4873/
1

# 2. 前端项目的运行与打包部署

# 2.1 Vue项目的运行及打包部署

Vue2.x官方文档:https://cn.vuejs.org/v2/guide/index.html

Vue3.x官方文档:https://v3.cn.vuejs.org/guide/introduction.html (opens new window)

# 2.1.1 Vue项目的运行

方法一:先执行npm install安装依赖,再执行npm run devnpm run serve运行Vue项目。

方法二:先执行yarn install安装依赖,再执行yarn run devyarn run serve运行Vue项目。

注:npm run servenpm run dev的区别就在于vue-cli脚手架的版本,yarn同理。

# 2.1.2 Vue项目修改启动端口号

[1] Vue 2.x项目修改启动端口号

config文件夹中有一个index.js其中部分内容如下,port即为端口号,在这里更改即可。

module.exports = {
    dev: {
        env: require('./dev.env'),
        port: 8080,    // 端口号
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
        proxyTable: {},
        // CSS Sourcemaps off by default because relative paths are "buggy"
        // with this option, according to the CSS-Loader README
        // (https://github.com/webpack/css-loader#sourcemaps)
        // In our experience, they generally work as expected,
        // just be aware of this issue when enabling this option.
        cssSourceMap: false,
      }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

[2] Vue 3.x项目修改启动端口号

Vue 3.x中修改端口号则需要在项目根目录下创建一个vue.config.js,内容如下。

module.exports = {
    devServer: {
        port: 8080,     // 端口号
    }
};
1
2
3
4
5

# 2.1.3 Vue项目的打包配置

[1] Vue 2.x项目的打包配置

Step1:将config/index.js中build处的assetsPublicPath由/改为./,代码如下:

  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: './',
1
2
3
4
5
6
7
8

注:如果未修改则生成的dist无法解析资源,无法使用

Step2:将build/utils.js中ExtractTextPlugin处添加publicPath:'../../',代码如下:

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        publicPath:'../../',
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12

[2] Vue 3.x项目的打包配置

在项目根目录新建一个vue.config.js文件,代码如下:

module.exports = {
    publicPath: './',
    outputDir: 'dist',
    lintOnSave: true,
    runtimeCompiler: true, //关键点在这
    // 调整内部的 webpack 配置。
    // 查阅 https://github.com/vuejs/vue-doc-zh-cn/vue-cli/webpack.md
    chainWebpack: () => {},
    configureWebpack: () => {},
    // 配置 webpack-dev-server 行为。
    devServer: {
        open: process.platform === 'darwin',
        host: '0.0.0.0',
        port: 8080,
        https: false,
        hotOnly: false,
        // 查阅 https://github.com/vuejs/vue-doc-zh-cn/vue-cli/cli-service.md#配置代理
        proxy: null, // string | Object
        before: app => {}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注:如果不配置打包后会出现空白页和路由不起作用的问题

# 2.1.4 打包Vue项目并部署到服务器

在Terminal处运行npm run build命令,出现如下内容则代表打包成功

注:服务器部署的话把打包生成的dist文件丢到网站根目录即可。

# 2.2 React项目的运行及打包部署

React官方文档:https://zh-hans.reactjs.org/docs/higher-order-components.html (opens new window)

# 2.2.1 React项目的运行

先执行yarn install安装依赖,再yarn start运行 React 项目。

# 2.2.2 打包React项目并部署到服务器

先执行yarn install安装依赖,再yarn build打包 React 项目。

打包完成后会生成一个build文件夹,服务器部署就把这个丢到网站根目录即可。

# 2.3 Angular项目的运行及打包部署

Angular官方文档:https://angular.cn/docs (opens new window)

# 2.3.1 Angular项目的运行

$ npm install -g @angular/cli   // 安装Angular CLI
$ npm install   // 安装依赖
$ npm start     // 运行项目
1
2
3

# 2.3.2 打包Angular项目并部署到服务器

$ npm run build
1

打包完成后会生成一个dist文件夹,服务器部署就把这个丢到网站根目录即可。

注意:不要使用 ng build --base-href ./ 命令进行打包,部署到服务器后会出现如下报错,导致刷新后页面空白。

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
1

Nginx示例配置:注意try_files一定要加,不然会出现子路由404的问题

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri.html $uri/ /index.html;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.4 以静态服务器的方式启动html项目

live-server 是一款npm工具,可以在项目目录启动一个node服务,然后直接在浏览器中预览,并且自动全局监听实时更新。

$ npm install -g live-server
$ live-server
1
2

# 2.5 使用JSON文件零编码构建假REST API

json-server是一个使用JSON文件零编码构建假REST API的工具,可用于生成模拟数据接口供前端进行测试。

$ npm install -g json-server
$ json-server --watch data.json
1
2

构建服务之后,便可在 localhost:3000 上直接调用接口,以下是一些常用的使用示例,详见官方README。

GET http://localhost:3000/data                       // 所有数据检索
GET http://localhost:3000/data?_sort=id&_order=asc   // 检索结果排序
GET http://localhost:3000/data/1                     // 单条数据检索
GET http://localhost:3000/data?_page=1&_limit=10     // 分页检索
GET http://localhost:3000/data?_start=20&_end=30     // 范围检索   
GET http://localhost:3000/data?title_like=商标        // 条件检索-like
GET http://localhost:3000/data?id_gte=10&id_lte=20   // 条件检索-范围
GET http://localhost:3000/data?id_ne=1               // 条件检索-排除
GET http://localhost:3000/data?id=1&id=2             // 多条件检索
GET http://localhost:3000/data?q=行政处罚             // 全文检索
1
2
3
4
5
6
7
8
9
10

# 2.6 将前端项目打包成客户端

将前端项目打包成客户端通常是使用 electron (opens new window) 开源库。

Github上有一个开源项目 nativefier (opens new window),只需要一个命令就可以将任意网站打包成桌面客户端。

  • 优点:简单易用,只需要一个很简单的命令就可以打包成客户端,Linux、Windows、MacOS均可使用。
  • 缺点:生成体积大,原因是因为用的是electron,而electron包含了chromium 和 nodejs,所以一般都会120多MB。

前提:1)macOS 10.9+ / Windows / Linux 2)Node.js >= 12.9 和 npm>= 6.9

安装:npm install -g nativefier

使用:nativefier 'URL' -p 'windows'(具体用法详见:API文档 (opens new window) ,输入nativefier --help也可查看)

# 3. 前端开源脚手架及组件库

# 3.1 前端脚手架

# 3.1.1 vue-element-admin

项目地址:https://github.com/PanJiaChen/vue-element-admin (opens new window)

官方文档:https://panjiachen.gitee.io/vue-element-admin-site/zh/guide/ (opens new window)

vue-element-admin

# 3.1.2 vue-admin-beautiful

项目地址:https://github.com/chuzhixin/vue-admin-beautiful-pro (opens new window)

vue-admin-beautiful

# 3.1.3 ant-design-vue-pro

项目地址:https://github.com/vueComponent/ant-design-vue-pro (opens new window)

官方文档:https://pro.antdv.com/docs/getting-started (opens new window)

ant-design-vue-pro

# 3.2 前端组件库

# 3.2.1 Element UI组件

Element UI 是一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。

官方文档:https://element.eleme.cn/#/zh-CN/component/installation (opens new window)

Element-UI

另注:还有一个基于Vue3的开源组件库 Naive UI (opens new window) 也很不错。

# 3.2.2 Echarts可视化图表

Echarts是百度开源的一个基于 JavaScript 的开源可视化图表库,用于制作可视化图表。

官方文档:https://echarts.apache.org/examples/zh/index.html (opens new window)

Echarts可视化

# 3.2.3 AntV X6图编辑引擎

AntV X6 是一个图编辑引擎,提供简单易用的节点定制能力和开箱即用的交互组件,方便我们快速搭建流程图、DAG 图、ER 图等图应用。业务场景示例 (opens new window)

项目地址:https://github.com/antvis/X6 (opens new window)

AntV_X6

# 4. 前端开发基础及JS封装

# 4.1 前端开发基础

HTML:HyperText Markup Language (超文本标记语言)“超文本”就是指页面内可以包含图片、链接,甚至音乐、程序等非文字元素。HTML不是一种编程语言,而是一种标记语言 (markup language)。

CSS:Cascading Style Sheets(层叠样式表),它是用于定义如何显示HTML 元素。CSS使得HTML写成的页面不那么单调,可以有各种颜色,大小等。

JavaScript:一种脚本语言,与Java语言没有关系,可插入HTML页面,使网页具有动态/交互性。

# 4.1.1 Html标记语言

1)添加内容

标题 <h# align="#">...</h#>
段落 <p>...</p>
空格 &nbsp;
版权符号 &copy;
注释 <!- -...- ->   另注Css内的注释是 /*...*/
换行 <br/>
水平分割线 <hr/>
居中 <center>...</center>
超链接 <a href="#" class="#">...</a>  注:target="_blank"在新窗口打开
图片 <img src="#" width="#" height="#" align="#"/>
视频 <source src="#" type='video/mp4'>
删除线 <strike>...</strike>
1
2
3
4
5
6
7
8
9
10
11
12

2)添加列表

有序列表<ol>+<li>
无序列表<ul>+<li>
定义列表<dl>+<dt>、<dd>
1
2
3

3)添加表格

表格 <table border="#" width="#" height="#">...</table>
行 <tr align="#" valign="top/middle/bottom">...</tr>
单元格 <td colspan/rowspan="合并列/合并行">...</td>
1
2
3

4)添加表单

1)表单 <form name="#" method="post/get" action="#">...</form>
       注:name:在表单的接收页面只接收有name属性的表单元素。
           action:接一个动态网页名
           enctype:若表单中有文件上传,则设置该属性为multipart/form-data
2)输入控件 <input type="#" name="#" value="默认值">
       注:type取值:text文本域   password 密码域    file文件域   checkbox复选框
                    radio单选框  button普通按钮  submit提交按钮  reset重置按钮
1
2
3
4
5
6
7

5)添加下拉列表/列表框

<select name="#" size="#">+<option value="1">  去掉size属性为列表框
1

6)其它

 1)多行文本域 <textarea name="#" cols="#" row="#" wrap="virtual">...</textarea>
 2)特殊边界 <fieldset>...</fieldset>   注:<legend>...</legend>为其定义标题
 3)改进可用性 <label for="#">...</label>
    注:当用户选择该标签时,浏览器自动将焦点转移到和标签相关的表单控件上。for属性应与相关元素的id属性相同
1
2
3
4

# 4.1.2 Css层叠样式表

1)样式表引入

外部样式表 <link rel="stylesheet" type="text/css" href="a1.css">
内部样式表 <style type="text/css">...</style>
内联样式表 <标记 style="...">
导入外部样式表 <style type="text/css">@import url;<style>
1
2
3
4

2)选择器

元素选择器 h1{color:red;font-size:25px;}
类选择器 .text1{color:blue;text-decoration:none;}    用class="#"调用
ID选择器 #menu{width:1280px;height:50px;}            用id="#"调用
伪类选择器 .text1:hover{color:red;text-decoration:underline;}
后代选择器 ul li{color:red;}
通配符选择器 *{margin:0px;padding:0px;}
1
2
3
4
5
6

3)内容属性

1)字体属性
   font:font-style|font-weight|font-size|line-height|font-family
            风格        粗细      字号        行高        字体
2)文本属性
     文本修饰:text-decoration:none(无)|underline(下划线)|line-throungh(删除线)
     文本对齐:text-align:left|center|right
     首行缩进:text-indent:2em
3)列表属性
     列表标记类型: list-style-type     图像符号: list-style-image:url
     注:属性值:none:无  disc:实心圆  circle:空心圆  square:实心方块  decimal:1 
                low-roman:i  upper-roman:I  lower-alpha:a  upper-alpha:A
4)背景属性
     背景颜色:background-color    背景图像:background-image
1
2
3
4
5
6
7
8
9
10
11
12
13

4)盒子属性

边框:border     边距:margin      内边距:padding
1)边框 border:border-width|border-style|color
   注:一个值:四边框同值       两个值:上下(1),左右(2)       四个值:上右下左
       border-style属性取值:double:双直线      ridge:3D凸线
2)边距 margin(同上)      特殊:margin:0 auto实现块级居中
3)内边距 padding(同上)
1
2
3
4
5
6

5)定位属性

1)相对定位 position:relative
   设置为相对定位的元素框会偏离某个距离。元素仍然保持其未定位前的形状,它原本所占的空间仍然保留。
   注:在使用相对定位时,无论是否进行移动(top、bottom、left、right),元素仍占据原来的空间。因此,移动元素会导致它覆盖其它框。
2)绝对定位 position:absolute
   设置为绝对定位的元素框从文档流完全删除,并相对于其包含块定位。元素定位后生成一个块级框。
   注:由于绝对定位与文档流无关,所以它们可以覆盖页面上的其它元素,可以通过设置z-index属性(属性值:数字)来控制这些框的堆放次序。
1
2
3
4
5
6

6)其它

1)浮动属性 float:left|right
2)框类型属性 display:none(隐藏)|block(块级)|inline(行内)
1
2

# 4.1.3 JavaScript脚本语言

1)脚本引入

1)位于head部分的脚本(为body区域程序代码调用事件处理函数)
   <script type="text/javascript">...</script>
2)位于body部分的脚本(执行后的输出成为页面内容)
   同上
3)直接位于事件处理部分的代码中
   <body onload="alert('欢迎');">
4)位于网页之外单独脚本文件
   <script src="text.js"></script>
5)使用javascript协议(<a>的href属性可用)
   <a href="javascript:alert('欢迎');>javascript</a>
1
2
3
4
5
6
7
8
9
10

2)函数

funcation 函数名(参数1,参数2,...,参数n){
          函数体;}
注:可以没有参数,但括号必须保留。声明参数不必明确类型
1
2
3

3)消息对话框

弹出对话框 alert("文本");
确认框 confirm("文本");
提示框 prompt("文本","默认值");
1
2
3

4)事件处理程序

1)调用
   通过html标记使用: <标记  事件="事件处理程序";>
   通过javascript代码使用: 对象.事件=事件处理程序;
2) 常用事件
   单击事件:onclick       鼠标获得焦点:onfocus        鼠标失去焦点:onblue
   表单提交事件:onsubmit   页面完成事件:onload   页面改变事件:onbeforeunload
   错误提示事件:onerror   鼠标悬停事件:onmouseover   域内容改变事件:onchange
1
2
3
4
5
6
7

# 4.2 JS公共方法封装

# 4.2.1 精确的计算加减乘除

涉及浮点数的时候,javascript的计算结果会有误差,需要精密计算的时候,可以自定义公共方法来解决。

     /**
      * 加法函数,用来得到精确的加法结果
      * 调用:add(a,b)
      * 返回值:a加上b的精确结果
      */
    add(a, b){
        let c = 0 // a的小数部分长度
        let d = 0 // b的小数部分长度
        try {
          c = a.toString().split('.')[1].length
        } catch (f) { }
        try {
          d = b.toString().split('.')[1].length
        } catch (f) { }
        let e = 10 ** Math.max(c, d) //保证a、b为整数的最小10次幂
        return (a * e + b * e) / e
    },
     /**
      * 减法函数,用来得到精确的减法结果
      * 调用:sub(a,b)
      * 返回值:a减去b的精确结果
      */
    sub(a, b){
        let c = 0 // a的小数部分长度
        let d = 0 // b的小数部分长度
        try {
            c = a.toString().split('.')[1].length
        } catch (f) { }
        try {
            d = b.toString().split('.')[1].length
        } catch (f) { }
        let e = 10 ** Math.max(c, d) //保证a、b为整数的最小10次幂
        return (a * e - b * e) / e
    },
    /**
     * 乘法函数,用来得到精确的乘法结果
     * 调用:mul(a,b)
     * 返回值:a乘以b的精确结果
     */
     mul(a, b){
        let c = 0 // a的小数部分长度
        let d = 0 // b的小数部分长度
        try {
          c = a.toString().split('.')[1].length
        } catch (f) { }
        try {
          d = b.toString().split('.')[1].length
        } catch (f) { }

        return (Number(a.toString().replace('.', '')) * Number(b.toString().replace('.', ''))) / (10 ** (c + d))
    },
    /**
     * 除法函数,用来得到精确的除法结果
     * 调用:div(a,b)
     * 返回值:a除以b的精确结果
     */
    div(a, b){
        let c = 0 
        let d = 0 
        try {
          c = a.toString().split('.')[1].length
        } catch (f) { }
        try {
          d = b.toString().split('.')[1].length
        } catch (f) { }
        const molecular = Number(a.toString().replace('.', '')) * (10 ** (c + d))
        const denominator = Number(b.toString().replace('.', '')) * (10 ** (c + d))
        return molecular / denominator / (10 ** (c - d))
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 4.2.2 表单输入值校验

   /**
     * 验证正整数+正小数+0
     */
    checkIsPositive(rule, value, callback){
        var reg = /^\d+(?=\.{0,1}\d+$|$)/
        if(reg.test(value)) {
            callback()
        }else{
            callback(new Error('请输入大于等于0的正数'))
        }
    },
    /**
     * 验证正整数+正小数
     */
    checkIsPositiveEx0(rule, value, callback){
        var reg = /^(\d|[1-9]\d+)(\.\d+)?$/
        if(reg.test(value)) {
            if(value == '0') {
                callback(new Error('请输入大于0的正数'))
            } else {
                callback()
            }
        }else{
            callback(new Error('请输入大于0的正数'))
        }
    },
    /**
     * 验证正整数+0
     */
    checkIsPositiveInteger(rule, value, callback){
        var reg = /^(0|[1-9][0-9]*)$/
        if(reg.test(value)) {
            callback()
        }else{
            callback(new Error('请输入大于等于0的正整数'))
        }
    },
    /**
     * 验证正整数
     */
    checkIsPositiveIntegerEx0(rule, value, callback){
        var reg = /^([1-9][0-9]*)$/
        if(reg.test(value)) {
            callback()
        }else{
            callback(new Error('请输入大于0的正整数'))
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 4.2.3 获取当前时间及当天的起始时间

    /**
     * 获取当前时间
     * @returns 
     */
    getDayTime: function(){
        var date=new Date();
        var year=date.getFullYear();
        var month=date.getMonth()+1;
        month=month>9?month:('0'+month);
        var day=date.getDate();
        day=day>9?day:('0'+day);
        var hh=date.getHours();
        hh=hh>9?hh:('0'+hh);
        var mm=date.getMinutes();
        mm=mm>9?mm:('0'+mm);
        var ss=date.getSeconds();
        ss=ss>9?ss:('0'+ss);
        var time=year+'-'+month+'-'+day+' '+hh+':'+mm+':'+ss;
        return time;
    },
    /**
     * 获取当天的起始时间
     * @returns 
     */
    getDayStartTime: function () {
        var date=new Date();
        var year=date.getFullYear();
        var month=date.getMonth()+1;
        month=month>9?month:('0'+month);
        var day=date.getDate();
        day=day>9?day:('0'+day);
        var time=year+'-'+month+'-'+day+' '+'00'+':'+'00'+':'+'00';
        return time;
    },
    /**
     * 获取当天的结束时间
     * @returns 
     */
    getDayEndTime: function () {
        var date=new Date();
        var year=date.getFullYear();
        var month=date.getMonth()+1;
        month=month>9?month:('0'+month);
        var day=date.getDate();
        day=day>9?day:('0'+day);
        var time=year+'-'+month+'-'+day+' '+'23'+':'+'59'+':'+'59';
        return time;
    },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 4.2.4 获取近x日的日期

    /**
     * 生成近x日的日期
     * @param {*} day 
     * @returns 
     */
    getDay(day){  
        var today = new Date();  
        var targetday_milliseconds=today.getTime() + 1000*60*60*24*day;
        today.setTime(targetday_milliseconds);
        var tYear = today.getFullYear();  
        var tMonth = today.getMonth();  
        var tDate = today.getDate();  
        tMonth = this.doHandleMonth(tMonth + 1);  
        tDate = this.doHandleMonth(tDate);  
        return tYear+"-"+tMonth+"-"+tDate; 
    },
    doHandleMonth(month){  
        var m = month;  
        if(month.toString().length == 1){  
            m = "0" + month;  
        }  
        return m;  
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4.2.5 过滤符合条件的JSON数组

       getFilterData(data,code){
            var dataFilter = data.filter((r) => {
                return r.code == code;
            });
            return dataFilter; 
        },
1
2
3
4
5
6

注:data是待过滤的JSON数组,code是JSON数组里用于过滤的项。

# 4.2.6 查找数组最大值并向上取整

需求情景:在绘制Echarts柱状图和折线图的时候,有时数据上限不能确定,这个方法可以根据返回数据的最大值动态获取上限。

   /**
     * 查找数字字符串数组中的最大值,并以千为单位向上取整
     * @param {*} arrs 
     * @returns 
     */
    findArrayMaxCeil(arrs){
        var max = parseFloat(arrs[0]);
        for(var i = 1, ilen = arrs.length; i < ilen; i+=1) {
            if(parseFloat(arrs[i]) > max) {
                max = parseFloat(arrs[i]);
            }
        }
        max = (Math.ceil(max/1000)*1000).toString();
        return max;
    },

  // 具体调用处
  min: 0,
  max: this.findArrayMaxCeil(this.data),
  interval: this.findArrayMaxCeil(this.data)/10,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5. Vue开发的常见问题

# 5.1 Vue生命周期

下图展示了Vue的生命周期,摘自Vue2.x官网。虽不需要立马弄明白所有东西,不过随着不断学习和使用,它的参考价值会越来越高。

vue生命周期

注:mounted和created的区别

  • created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。

  • mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

通常created使用的次数多,而mounted通常是在一些插件的使用或者组件的使用中进行操作。比如在created中无法对Echarts进行一些初始化配置,一定要等这个html渲染完后才可以进行,那么mounted就是不二之选。

# 5.2 Vue父子组件互相调用

# 5.2.1 父组件获取子组件的数据和方法

Step1:调用子组件的时候,定义一个ref

<headerchild ref="headerChild"></headerchild>
1

Step2:在父组件里面通过如下形式进行调用

this.$refs.headerChild.属性
this.$refs.headerChild.方法
1
2

# 5.2.2 子组件获取父组件的数据和方法

在子组件里面通过如下形式进行调用

this.$parent.属性
this.$parent.方法
1
2

注:使用this.$parent.属性有时会出现调不到父组件属性的情况,此时建议使用props将父组件属性传递给子组件,下面是一个示例。

父组件:

<template>
  <div>
    父组件:
    <input type="text" v-model="name">
    <!-- 引入子组件 -->
    <child :inputName="name"></child>
  </div>
</template>
<script>
  import child from './child'
  export default {
    components: {
      child
    },
    data () {
      return {
        name: ''
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

子组件:

<template>
  <div>
    子组件:
    <span>{{inputName}}</span>
  </div>
</template>
<script>
  export default {
    // 接受父组件的值
    props: ['inputName']
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12

# 5.3 点击父组件重新加载子组件

我们经常会遇到通过给在父组件中引用的子组件标签上添加属性,来渲染可以拥有不同数据的子组件的需求。由于子组件的 created() 生命周期函数只执行一次,因此可能会出现数据没有更新的情况。

这时候就用到了 Vue 中的 key 属性,它是用来给 Vue 元素渲染的时候用的,每次渲染的时候会去拿这个 key 值做对比,如果这一次的key 值和上一次的 key 值是不一样的才会重新渲染dom 元素,否则保持上一次的元素状态。

根据这个原理我们可以给 key 直接绑定一个 时间戳,点击父组件的时候改变这个时间戳即可实现子组件的重新加载。

父组件:

<template>
    <div>
        <div>
            <h1>父级</h1>
            <button @click="handleLoad">点击重新加载子级</button>
        </div>
        <children :key="timer"></children>
    </div>
</template>
<script>
import children from '@/components/parent/children'
export default {
    name: 'parent',
    components: { children },
    data () {
        return {
            timer: ''
        }
    },
    methods: {
        handleLoad () {
            this.timer = new Date().getTime()
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

子组件:

<template>
    <div>
        子级
    </div>
</template>
<script>
export default {
    name: 'children',
    data () {
        return {}
    },
    created () {
        console.log('重新加载啦')
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 5.4 Echarts组件多次调用出现id重复问题

需求情景:有时候我们会用到同一个Echarts样式,只是在不同地方灌入不同的数据,用id作为选择器的时候只会渲染一个。为了解决这个问题,我们可以将Echarts封装成组件调用,干掉id,使用ref。(注:写到一个文件里的话用ref也是不行的)

//修改前
<div id="chart"></div>
let myChart = this.$echarts.init(document.getElementById(chart));

//修改后
<div ref="chart"></div>
let myChart = this.$echarts.init(this.$refs.chart);
1
2
3
4
5
6
7

# 5.5 VueX状态管理

VueX 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

官方文档:https://vuex.vuejs.org/zh/ (opens new window) (有关VueX的调试建议使用官方的 Vue.js devtools (opens new window) 扩展,里面有个专门的VueX模块)

VueX有State、Getters、Mutations、Actions、Modules这几个核心概念,建议先把它看明白再来用,具体见官方文档。

# 5.5.1 解决VueX页面刷新导致数据丢失问题

VueX的数据是存在内存当中的,当页面刷新之后,VueX的数据自然会丢失。我们可以使用localStorage或者sessionStroage来解决,在页面刷新时把VueX的值存起来,再在页面加载时将其取出替换,代码如下:

created() {
    //在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem("store")) {
      this.$store.replaceState(
        Object.assign(
          {},
          this.$store.state,
          JSON.parse(sessionStorage.getItem("store"))
        )
      );
    }
    //在页面刷新时将VueX里的信息保存到sessionStorage里
    window.addEventListener("beforeunload", () => {
      sessionStorage.setItem("store", JSON.stringify(this.$store.state));
    });
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

注:可以使用sessionStorage.removeItem("store")来清除缓存数据。

# 5.5.2 一个简单的VueX使用示例

系统字典一般都是动态的,后端专门提供一个查询系统字典的接口。然后前端在请求接口、渲染页面的时候,通过系统字典把抽象的数据转换成展示字段。但是这个过程我们不可能让它每次转换都要请求一次字典接口,因此我们可以在登录的时候把它存入到VueX里缓存起来,每次转换的时候请求这个即可。下面是一个简单的示例:

[1] 接口请求及公共函数

/api/code/index.js

import axios from '@/api'

export const getCodeData = (data) => {
    return axios.request({
        url:'/code',
        method:'post',
        data:data
    });
};
1
2
3
4
5
6
7
8
9

common/common.js

import { getCodeData } from '@/api/code'

setCodeList(){
    this.request(getCodeData,'',this,data => {
        this.$store.commit("code/setCodeList", {  
            codeList: data
        });
    })
},
1
2
3
4
5
6
7
8
9

[2] VueX相关配置

/store/modules/code.js

const state = {
    codeList: []
}
const getters = {
    codeList:state => state.codeList,
}
const actions = {}
const mutations = {
    setCodeList(state, codeList) { 
        state.codeList = codeList;
    }
}

export default {
    namespaced: true,
    state,
    getters,
    actions,
    mutations
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

/store/modules/index.js

import code from './code.js' 

export default {
    code
}
1
2
3
4
5

/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state.js'
import mutations from './mutations.js'
import actions from './actions.js'
import getters from './getters.js'
import modules from './module/index.js'

Vue.use(Vuex);

export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters,
    modules
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

[3] VueX的调用

登录逻辑里的相关部分

sessionStorage.removeItem("codeList") // 清除sessionStorage里缓存的codeList
this.setCodeList(); // 请求接口,将系统字典存入VueX
setTimeout(() =>{
    this.$router.push('/'); // 设置延时是为了解决登录时VueX还没请求完就加载页面的问题。
},1000);
1
2
3
4
5

在首页里加上上述“解决VueX页面刷新导致数据丢失问题”的代码,此处不再赘述。

筛选出指定类型的系统字典可以在公共方法里写一个过滤器来实现,比如:

getFilterCodeData: function (res, code, value) {
    var codeFilter = res.filter((r) => {
        return r.code == code;
    });
    return codeFilter; 
}
1
2
3
4
5
6

具体页面的调用转换

    computed:{
        codeList(){
            return this.$store.state.code.codeList.codeList;
        }
    },

    // 在具体方法里用this.codeList获取字典,再调用过滤器筛选,再将接口返回值通过字典转换成展示值即可
1
2
3
4
5
6
7

# 5.6 Element table的tooltip设置宽度不生效问题

<style lang="css">
.el-tooltip__popper {
    max-width: 400px;
    line-height: 180%;
}
</style>
1
2
3
4
5
6

注意:修改elementui自带样式的话,不能在<style scoped></style>中修改,因为不会生效。

# 5.7 MD5消息摘要算法

MD5算法是典型的消息摘要算法,可采用MD5算法校验文件完整性,计算目标文件的数字指纹,前端的用法如下:

Step1:先下载md5的包

$ npm install js-md5 --save-dev
1

Step2:按需引入并使用

import md5 from "js-md5"; // 引用
md5(this.encryption)      // 使用
1
2

注意:不要拿MD5算法当加密算法用,它可以被完全破解,不要用它来加密涉及用户密码这种敏感的信息,加密这种信息建议使用AES、RSA等非对称加密算法。

# 6. 使用Vue实现的一些前端效果

# 6.1 动态粒子特效背景

下图那些类似于星座图的点和线,是由vue-particles生成的,不仅自己动,而且能与用户鼠标事件产生互动,是非常炫的一种动态特效,可以在Vue项目中使用,需要安装第三方依赖。

Vue动态粒子特效

Step1:安装vue-particles

$ npm install --save vue-particles
1

Step2:引入 main.js 文件

import Vue from 'vue'
import VueParticles from 'vue-particles'
Vue.use(VueParticles)
1
2
3

Step3:具体使用

<template>
    <div id="app">
        <vue-particles
                 class="login-background"
                 color="#97D0F2"
                 :particleOpacity="0.7"
                 :particlesNumber="50"
                 shapeType="circle"
                 :particleSize="4"
                 linesColor="#97D0F2"
                 :linesWidth="1"
                 :lineLinked="true"
                 :lineOpacity="0.4"
                 :linesDistance="150"
                 :moveSpeed="3"
                 :hoverEffect="true"
                 hoverMode="grab"
                 :clickEffect="true"
                 clickMode="push">
         </vue-particles>
    </div>
 </template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

解释:

color: String类型。默认’#dedede’。粒子颜色。
particleOpacity: Number类型。默认0.7。粒子透明度。
particlesNumber: Number类型。默认80。粒子数量。
shapeType: String类型。默认’circle’。可用的粒子外观类型有:“circle”,“edge”,“triangle”,“polygon”,“star”。
particleSize: Number类型。默认80。单个粒子大小。
linesColor: String类型。默认’#dedede’。线条颜色。
linesWidth: Number类型。默认1。线条宽度。
lineLinked: 布尔类型。默认true。连接线是否可用。
lineOpacity: Number类型。默认0.4。线条透明度。
linesDistance: Number类型。默认150。线条距离。
moveSpeed: Number类型。默认3。粒子运动速度。
hoverEffect: 布尔类型。默认true。是否有hover特效。
hoverMode: String类型。默认true。可用的hover模式有: “grab”, “repulse”, “bubble”。
clickEffect: 布尔类型。默认true。是否有click特效。
clickMode: String类型。默认true。可用的click模式有: “push”, “remove”, “repulse”, “bubble”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

说明:通常使用我们都是直接引入这个标签,然后在这个标签后面放上自己的内容,如果要当作背景的话,设置定位position:absolute或者position:fixed

# 6.2 实时显示当前时间每秒更新

可视化大屏一般都是全屏展示的,所以项目可能会在左上角或者右上角实时显示当前时间。以下代码可以实现yyyy-MM-dd hh:mm:ss格式当前时间每秒更新的效果。

<div id="app">
    <b>{{dateFormat(date)}}</b>
</div>

<script>
export default {
  data() {
    return {
      date: new Date()
    };
  },
  mounted() {
    let _this = this; // 声明一个变量指向Vue实例this,保证作用域一致
    this.timer = setInterval(() => {
      _this.date = new Date(); // 修改数据date
    }, 1000)
  },
  beforeDestroy() {
    if (this.timer) {
      clearInterval(this.timer); // 在Vue实例销毁前,清除我们的定时器
    }
  },
  methods:{
      dateFormat(time) {
      var date=new Date(time);
      var year=date.getFullYear();
      // 在日期格式中,月份是从0开始的,因此要加1。使用三元表达式在小于10的前面加0,以达到格式统一。
      var month= date.getMonth()+1<10 ? "0"+(date.getMonth()+1) : date.getMonth()+1;
      var day=date.getDate()<10 ? "0"+date.getDate() : date.getDate();
      var hours=date.getHours()<10 ? "0"+date.getHours() : date.getHours();
      var minutes=date.getMinutes()<10 ? "0"+date.getMinutes() : date.getMinutes();
      var seconds=date.getSeconds()<10 ? "0"+date.getSeconds() : date.getSeconds();
      // 拼接
      return year+"-"+month+"-"+day+" "+hours+":"+minutes+":"+seconds;
      }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 6.3 基于Element区间范围组件

管理系统中经常会有要求对某个字段进行区间阈值设置或者作为筛选条件的需求,而Element中除了日期区间选择之外并没有特别契合的组件,可以自己手动写一个。

主要思路:[1] 单个表单校验:必填项校验、正整数校验、区间校验 [2] 关联校验:右侧阈值不得小于左侧阈值

<template>
  <el-form ref="form" :model="form" :rules="rules">
    <el-form-item prop="min">
      <el-input v-model="form.min" @change="handleMinChange" />
    </el-form-item>
    ~
    <el-form-item prop="max">
      <el-input v-model="form.max" @change="handleMaxChange" />
    </el-form-item>
  </el-form>
</template>

<script>
const MIN_NUMBER = 1;
const MAX_NUMBER = 100000;

export default {
  data() {
    return {
      form: { min: '20', max: '100000' },
      rules: {
        min: [
          { required: true, message: '必填项,请维护', trigger: 'blur' },
          { validator: this.validateCom, trigger: 'blur' },
          { validator: this.validateMin, trigger: 'blur' },
        ],
        max: [
          { required: true, message: '必填项,请维护', trigger: 'blur' },
          { validator: this.validateCom, trigger: 'blur' },
          { validator: this.validateMax, trigger: 'blur' },
        ],
      },
    };
  },
  methods: {
    getFormData() {
      const ret = {};
      this.$refs.form.validate((valid) => {
        ret.valid = valid;
        ret.form = this.form;
      });
      return ret;
    },
    resetForm() {
      this.$refs.form.resetFields();
    },
    handleMinChange() {
      this.$refs.form.validateField('max');
    },
    handleMaxChange() {
      this.$refs.form.validateField('min');
    },
    validateCom(rule, value, callback) {
      const one = Number(value);
      if (Number.isInteger(one)) {
        if (one < MIN_NUMBER) {
          return callback(new Error('输入值必须大于0'));
        } else if (one > MAX_NUMBER) {
          return callback(new Error('输入值必须小于100000'));
        }
        return callback();
      }
      return callback(new Error('输入值必须为正整数'));
    },
    validateMin(rule, value, callback) {
      const one = Number(value);
      const max = Number(this.form.max);
      if (!max || one < max) {
        return callback();
      }
      return callback(new Error('输入值不得大于最大阈值'));
    },
    validateMax(rule, value, callback) {
      const one = Number(value);
      const min = Number(this.form.min);
      if (!min || one > min) {
        return callback();
      }
      return callback(new Error('输入值不得小于最小阈值'));
    },
  },
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 6.4 基于Echarts引入中国地图

Echarts中国地图

先用npm安装一下echarts依赖

$ npm install echarts --save
1

再去网上搜一个china.js文件(官方已停止下载,但很好搜,随便找个带中国地图的项目里就有),放到src/asset/js目录下。

下面给出上图实现效果的简单代码示例:

<template>
    <div class="home">
        <div class="chinaMap" id="chinaMap" style="width: 600px;height:600px;">
        </div>
    </div>
</template>

<script> 

import * as echarts from 'echarts'
import '../../../../src/assets/js/china.js'

export default {
    name:'china-map',
    mounted() {
        this.chinaMap();
    },
    methods: {
        chinaMap(){
            var data = [
                {name: '深圳', value: [114.07,22.62]},
                {name: '北京', value: [116.46,39.92]},
                {name: '杭州', value: [120.19,29.96]},
                {name: '苏州', value: [120.62,32.62]},
                {name: '成都', value: [104.06,30.67]},
                {name: '广州', value: [113.23,23.76]},
                {name: '上海', value: [121.48,31.22]},
            ]
            // 基于准备好的dom,初始化echarts实例
            var myChart = echarts.init(document.getElementById('chinaMap'));

            // 指定图表的配置项和数据
            var option = {
                tooltip : {
                    trigger: 'none',
                },
                geo: {
                    map: 'china',
                    aspectScale: 0.75, //长宽比
                    layoutCenter: ['50%', '50%'],
                    layoutSize: 600, 
                    itemStyle: {
                        // 定义地图样式
                        normal: {                    // 普通状态下的样式
                            areaColor: '#cfcfcf',
                            borderColor: '#fff',
                            show: false
                        },
                        emphasis: {                    // 高亮状态下的样式
                            areaColor: '#cfcfcf',
                            borderColor: '#fff',
                            show: false
                        }
                    },
                    select:{
                        label:{
                            show: false
                        },
                        itemStyle:{
                            color: '#fff'
                        }
                    },
                    label:{ //不显示省份名称
                        normal: {
                            show: false
                        },
                        emphasis: {
                            show: false
                        }
                    },
                },
                series : [
                    {
                        coordinateSystem: 'geo', // series坐标系类型
                        type: 'scatter', //散点图
                        mapType: 'china',
                        roam: false,
                        data: data,
                        symbol:'path://M512 736.981333L775.68 896l-69.76-299.904L938.666667 394.410667l-306.816-26.325334L512 85.333333 392.149333 368.085333 85.333333 394.410667l232.746667 201.685333L248.32 896z',
                        symbolSize: 12,
                        encode: {
                            value: 2
                        },
                        showEffectOn: 'render',
                        rippleEffect: {
                            brushType: 'stroke'
                        },
                        hoverAnimation: true,
                        label: {
                            formatter: '{b}',
                            position: 'right',
                            lineHeight : 15,
                            show: true,
                            emphasis:{
                                textStyle:{
                                    color: '#EC652A' //设置活动状态下字体样式,会跟随散点高亮
                                }
                            }
                        },
                        itemStyle: {
                            normal: {            // 散点图标普通状态下的样式
                                color: '#333'
                            },
                            emphasis: {            // 散点图标高亮状态下的样式
                                color:'#EC652A',
                            },
                            shadowBlur: 10,
                            shadowColor: '#333'
                        },
                        zlevel: 1,
                    }
                ]
            };
            if (option && typeof option === "object") {
                myChart.setOption(option, true);
            }
        }
    },
}
</script>

<style lang="css" scoped>
.chinaMap{
    margin: 0 auto;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

具体样式根据自己需求进行调整吧,如果需要可下钻的地图,请参考:https://github.com/xiaofan9/echarts-china-map (opens new window) 项目

# 6.5 Echarts柱状折线混合图例

以下是一个柱状折线混合的Echarts图例,也可以用它轻易的拆分出单独的柱状图或折线图。

Echarts柱状折线混合图例

<template>
    <div class="home">
        <div class="mixChart" id="mixChart" style="width: 1500px;height:500px;">
        </div>
    </div>
</template>

<script> 
import * as echarts from 'echarts'

export default {
    name:'mixChart',
    mounted() {
        this.trendChart();
    },
    methods: {
        trendChart(){
            var myChart = echarts.init(document.getElementById('mixChart'));
            var option = {
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'cross',
                        crossStyle: {
                            color: '#999'
                        }
                    }
                },
                legend: {
                    data: ['蒸发量', '降水量', '平均温度']
                },
                xAxis: [
                    {
                        type: 'category',
                        data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
                        axisPointer: {
                            type: 'shadow'
                        }
                    }
                ],
                yAxis: [
                    {
                        type: 'value',
                        name: '水量',
                        min: 0,
                        max: 250,
                        interval: 50,
                        axisLabel: {
                            formatter: '{value} ml'
                        }
                    },
                    {
                        type: 'value',
                        name: '温度',
                        min: 0,
                        max: 25,
                        interval: 5,
                        axisLabel: {
                            formatter: '{value} °C'
                        }
                    }
                ],
                series: [
                    {
                        name: '蒸发量',
                        type: 'bar',
                        data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3]
                    },
                    {
                        name: '降水量',
                        type: 'bar',
                        data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3]
                    },
                    {
                        name: '平均温度',
                        type: 'line',
                        yAxisIndex: 1,
                        data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2]
                    }
                ]
            };
            myChart.setOption(option);
        }

    },
}
</script>

<style lang="css" scoped>
.mixChart{
    margin: 0 auto;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

# 6.6 Vue解析md文件并渲染样式

需求情景:比如将README说明文档展示到前端页面,可以通过Vue直接解析md文件渲染样式实现。

Step1:安装解析md文件和渲染样式的包

$ npm install markdown-loader [email protected] --save     // 解析md文件
$ npm install github-markdown-css --save                   // 渲染样式
1
2

注:html-loader安装0.5.5版本,用最新版的话后续会报Module build failed (from ./node_modules/**html-loader**/dist/cjs.js): TypeError: this.getOptions is not a function的错儿,无法解析。

Step2:打开webpack.config.js文件,添加如下规则:

  {
    test: /\.md$/,
    use: [
      {
        loader: "html-loader"
      },
      {
        loader: "markdown-loader",
        options: {}
      }
    ]
  },
1
2
3
4
5
6
7
8
9
10
11
12

注:如果不添加规则的话后续会报You may need an appropriate loader to handle this file type.的错儿,无法解析。

Step3:在main.js中将github-markdown-css导入

import 'github-markdown-css';
1

Step4:vue解析md并渲染样式

<template>
    <div v-html="md" class="markdown-body" ></div>
</template>

<script>
import demo from './md/README.md';
export default {
    name: 'test',
    data () {
        return {
            md: demo
        };
    },
    methods: {}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 6.7 基于Element引入一键置顶按钮

官方文档对于一键置顶功能的实现描述不够详细,这里结合上述Markdown页面给出一个示例:

<template>
    <div class="wraper">
        <template>
            <el-backtop target=".wraper">
                <div class ='up'>UP</div>
            </el-backtop>
        </template>
        <div v-html="md" class="markdown-body"></div>      
    </div>
</template>

<script>
import   from './md/README.md';
export default {
    name:'test',
    data () {
        return {
            md: demo
        };
    },
}
</script>

<style lang="css" scoped>
.wraper {
    height: 100vh;
    overflow-x: hidden;
}
.up{
    height: 100%;
    width: 100%;
    background-color: #f2f5f6;
    box-shadow: 0 0 6px rgba(0,0,0, .12);
    text-align: center;
    line-height: 40px;
    color: #1989fa;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

注:target 跟的是触发滚动的对象,如果这个页面是作为一个组件嵌入到公共页面里,那么这里的 target 要用外部的类选择器,然后底下的.wraper也要改一下名字。

# 6.8 使用D3.js库绘制知识图谱

KnowledgeGraph.vue

<template>
  <div class="knowledgeGraph" id="graph" :style="{height: graphHeight}"></div>
</template>

<script>
import * as d3 from "d3";

export default {
  data() {
    return {
      colorData: ["#1f77b4", "#2ca02c", "#ff7f0e"],
      legendData: ["执法主体", "对应责任", "执法依据"],
      originalGraphData: [
        {
          "subject": "国家版权局",
          "according": "《中华人民共和国著作权法》(2010年修订)第四十七条",
          "responsibility": "协调版权纠纷、处理版权侵权投诉、监督版权行业、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家知识产权局",
          "according": "《中华人民共和国商标法》(2019年修订)第五十八条",
          "responsibility": "商标注册、商标转让审批、商标侵权调查、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家版权局",
          "according": "《中华人民共和国网络安全法》(2017年修订)第三十五条",
          "responsibility": "保护网络版权、处理网络版权侵权投诉、协调网络版权纠纷、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家知识产权局",
          "according": "《中华人民共和国专利法》(2008年修订)第十条",
          "responsibility": "处理专利申请、审查专利申请、公布专利信息、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家版权局",
          "according": "《中华人民共和国著作权法》(2010年修订)第二十一条",
          "responsibility": "处理著作权申请、审查著作权申请、公布著作权信息、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家版权局",
          "according": "《中华人民共和国著作权法》(2010年修订)第九十九条",
          "responsibility": "协调版权纠纷、处理版权侵权投诉、监督版权行业、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家知识产权局",
          "according": "《中华人民共和国商标法》(2019年修订)第百条",
          "responsibility": "商标注册、商标转让审批、商标侵权调查、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家知识产权局",
          "according": "《中华人民共和国商标法》(2019年修订)第五十八条",
          "responsibility": "商标转让审批、商标侵权调查、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家版权局",
          "according": "《中华人民共和国著作权法》(2010年修订)第四十七条",
          "responsibility": "协调版权纠纷、处理版权侵权投诉、其他法律法规规章文件规定应履行的责任"
        },
        {
          "subject": "国家知识产权局",
          "according": "《中华人民共和国网络安全法》(2017年修订)第三十五条",
          "responsibility": "保护网络版权、处理网络版权侵权投诉、其他法律法规规章文件规定应履行的责任"
        }
      ],
      graphHeight: ''
    }
  },
  mounted() {
    var radius = 15; // 节点的半径
    this.graphHeight = window.innerHeight * 0.9
    var width = document.getElementById('graph').offsetWidth * 0.9
    var height = this.graphHeight * 0.9
    var color = d3.scaleOrdinal(this.colorData);

    function convertTriples(data) {
      const nodes = [];
      const links = [];
      let node_id = 0;
      const node_map = {};

      for (const item of data) {
        const subject = item.subject;
        if (!(subject in node_map)) {
          nodes.push({ id: node_id, name: subject, group: 1 });
          node_map[subject] = node_id;
          node_id += 1;
        }

        const responsibility = item.responsibility;
        if (!(responsibility in node_map)) {
          nodes.push({ id: node_id, name: responsibility, group: 2 });
          node_map[responsibility] = node_id;
          node_id += 1;
        }

        const law_basis = item.according;
        if (!(law_basis in node_map)) {
          nodes.push({ id: node_id, name: law_basis, group: 3 });
          node_map[law_basis] = node_id;
          node_id += 1;
        }

        links.push({ source: node_map[subject], target: node_map[responsibility], value: 1 });
        links.push({ source: node_map[responsibility], target: node_map[law_basis], value: 1 });
      }

      return { nodes, links };
    }

    var inputGraph = convertTriples(this.originalGraphData);

    var tooltipDiv = d3.select(this.$el).append("div")
        .attr("class", "tooltip")
        .style("opacity", 0);
    // 创建一个临时的 div 元素用于测量,避免频繁的 DOM 操作
    var div = document.createElement("div");
    div.style.width = "300px";
    div.style.fontFamily = "Arial, sans-serif";
    div.style.fontSize = "12px";
    div.style.wordWrap = "break-word";
    document.body.appendChild(div);
    // 在节点数据中添加边界框信息
    inputGraph.nodes.forEach(function(node) {
      div.textContent = node.name;
      node.bbox = { width: div.offsetWidth, height: div.offsetHeight };
    });
    // 删除临时元素
    document.body.removeChild(div);

    function forceBoundary(x1, y1, x2, y2) {
      return function(d) {
        if (d.x < x1 + radius) d.x = x1 + radius;
        if (d.x > x2 - radius) d.x = x2 - radius;
        if (d.y < y1 + radius) d.y = y1 + radius;
        if (d.y > y2 - radius) d.y = y2 - radius;
      };
    }

    // 在模拟中使用边界力
    var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(150))
        .force("charge", d3.forceManyBody().strength(-5))  // 增加排斥力
        .force("center", d3.forceCenter(width / 2, height / 2))
        .force("boundary", forceBoundary(0, 0, width, height));

    simulation
        .nodes(inputGraph.nodes)
        .on("tick", ticked);

    simulation.force("link")
        .links(inputGraph.links);

    var selectedNode = null  // 用于跟踪当前选定的节点

    // 在SVG上添加点击事件
    var svg = d3.select(".knowledgeGraph").append("svg")
        .attr("width", width)
        .attr("height", height)
        .on('dblclick', function() {
          selectedNode = null
          // 遍历所有节点,根据其分组来设置透明度
          node.transition()
              .style("opacity", function(node_d) {
                return activeLegends.includes(node_d.group) ? 0 : 1;
              });
          // 遍历所有边,根据其分组来设置透明度
          link.transition()
              .style("opacity", function(link_d) {
                return activeLegends.includes(link_d.source.group) || activeLegends.includes(link_d.target.group) ? 0 : 1;
              });
          // 重新启动模拟
          simulation.alpha(1).restart();
        });

    var g = svg.append("g");

    var link = g.append("g")
        .attr("class", "links")
        .selectAll("line")
        .data(inputGraph.links)
        .enter().append("line")
        .attr("stroke-width", function(d) { return Math.sqrt(d.value)*2; })
        .attr("pointer-events", "none"); // 禁止线接收鼠标事件,防止因为线影响了节点的点击效果

    var node = g.append("g")
        .attr("class", "nodes")
        .selectAll("circle")
        .data(inputGraph.nodes)
        .enter().append("circle")
        .attr("r", radius)
        .attr("fill", function(d) { return color(d.group); })
        .call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended))
        .on("mouseover", function(event, d) {
          // 检查节点的透明度
          if (d3.select(this).style('opacity') === '0') {
            return; // 如果节点被隐藏,则不执行悬浮效果
          }
          mouseover(event, d);
        })
        .on("mouseout", mouseout)
        .on('click', function(event, d) {
          // 检查节点的透明度
          if (d3.select(this).style('opacity') === '0') {
            return; // 如果节点被隐藏,则不执行任何操作
          }
          selectedNode = d;  // 更新选定的节点
          event.stopPropagation();
          node.style('opacity', 0);
          link.style('opacity', 0);
          var connectedNodes = getConnectedNodes(d);
          var connectedLinks = getConnectedLinks(d);
          // 根据激活图例来设置连接到选定节点的节点和边的透明度
          connectedNodes.forEach(function(node) {
            if (!activeLegends.includes(d3.select(node).data()[0].group)) {
              d3.select(node).style('opacity', 1);
            }
          });
          connectedLinks.forEach(function(link) {
            const linkData = d3.select(link).data()[0];
            if (!activeLegends.includes(linkData.source.group) && !activeLegends.includes(linkData.target.group)) {
              d3.select(link).style('opacity', 1);
            }
          });
          d3.select(this).style('opacity', 1);

          if (d3.select(this).style('opacity') === 0) {
            return;
          }

          // 找到透明度为1的节点
          var activeNodes = inputGraph.nodes.filter(function(nodeData) {
            return node.filter(function(d) { return d.id === nodeData.id; }).style('opacity') === '1';
          });
          // 添加或更新碰撞检测力
          simulation.force("collision", d3.forceCollide(function(d) {
            // 只对透明度为1的节点应用碰撞检测力
            return activeNodes.some(function(activeNode) { return activeNode.id === d.id; }) ? radius : 0;
          }));
          // 重新启动模拟
          simulation.alpha(1).restart();

          event.stopPropagation();
        });

    var legend = svg.append("g")
        .attr("class", "legend")
        .attr("transform", "translate(" + (width - 80) + "," + 20 + ")")
        .on("dblclick", function(event) {
          event.stopPropagation(); // 防止点击legend过快时误触发双击展示全部节点
        })
        .selectAll("g")
        .data(this.legendData)
        .enter().append("g")
        .attr("transform", function(d, i) { return "translate(0," + (i * 20) + ")"; })

    legend.append("rect")
        .attr("width", 18)
        .attr("height", 18)
        .style("fill", function(d, i) { return color(i + 1); })
        .style("cursor", "pointer"); // 将鼠标悬浮样式设置为手型

    legend.append("text")
        .attr("x", 24)
        .attr("y", 9)
        .attr("dy", ".35em")
        .text(function(d) { return d; });

    // 初始化一个数组来保存激活的图例索引
    var activeLegends = [];

    // 图例点击事件
    legend.selectAll("rect").on("click", (event, d) => {
      var rectElement = d3.select(event.currentTarget);
      var parentGElement = d3.select(event.currentTarget.parentNode);
      var i = this.legendData.indexOf(d); // 获取索引

      // 切换激活状态
      var isActive = rectElement.classed("active");
      rectElement.classed("active", !isActive);
      rectElement.classed("inactive", isActive);

      // 更新激活的图例数组
      if (!isActive) {
        activeLegends.push(i + 1); // 添加到激活数组
      } else {
        var index = activeLegends.indexOf(i + 1);
        if (index > -1) {
          activeLegends.splice(index, 1); // 从激活数组中移除
        }
      }

      // 如果有选定的节点,仅更新与选定节点有关的节点和连线的透明度
      if (selectedNode) {
        // 重新计算与 selectedNode 相关的节点和边
        let connectedNodes = getConnectedNodes(selectedNode);
        let connectedLinks = getConnectedLinks(selectedNode);

        // 更新透明度
        connectedNodes.forEach((node) => {
          if (!activeLegends.includes(d3.select(node).data()[0].group)) {
            d3.select(node).style('opacity', 1);
          } else {
            d3.select(node).style('opacity', 0);  // 这一行是新添加的
          }
        });

        connectedLinks.forEach((link) => {
          const linkData = d3.select(link).data()[0];
          if (!activeLegends.includes(linkData.source.group) && !activeLegends.includes(linkData.target.group)) {
            d3.select(link).style('opacity', 1);
          } else {
            d3.select(link).style('opacity', 0);
          }
        });

        if (!activeLegends.includes(selectedNode.group)) {
          d3.select(node.filter((d) => d.id === selectedNode.id).nodes()[0]).style('opacity', 1);
        } else {
          d3.select(node.filter((d) => d.id === selectedNode.id).nodes()[0]).style('opacity', 0);
        }
      } else {
        // 如果没有选定的节点,更新所有节点和连线的透明度
        node.transition()
            .style("opacity", function(node_d) {
              return activeLegends.includes(node_d.group) ? 0 : 1;
            });

        link.transition()
            .style("opacity", function(link_d) {
              return activeLegends.includes(link_d.source.group) || activeLegends.includes(link_d.target.group) ? 0 : 1;
            });
      }

      // 更新图例自身和文字的样式
      parentGElement.transition()
          .style("opacity", isActive ? 1 : 0.5);

      // 阻止事件冒泡,以避免触发 svg 的点击事件
      event.stopPropagation();
    });

    function ticked() {
      // 更新链接位置
      link.each(function(d) {
        d3.select(this)
            .attr("x1", d.source.x)
            .attr("y1", d.source.y)
            .attr("x2", d.target.x)
            .attr("y2", d.target.y);
      });

      // 更新节点位置
      node.each(function(d) {
        d.x = Math.max(radius, Math.min(width - radius, d.x));
        d.y = Math.max(radius, Math.min(height - radius, d.y));
        d3.select(this)
            .attr("cx", d.x)
            .attr("cy", d.y);
      });

    }

    function dragstarted(event, d) {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(event, d) {
      var currentZoom = d3.zoomTransform(g.node());
      var mouseX = (event.x - currentZoom.x) / currentZoom.k;
      var mouseY = (event.y - currentZoom.y) / currentZoom.k;

      d.fx = Math.max(radius, Math.min(width - radius, mouseX));
      d.fy = Math.max(radius, Math.min(height - radius, mouseY));
    }

    function dragended(event) {
      if (!event.active) simulation.alphaTarget(0);
    }

    function getConnectedNodes(selectedNode, visitedNodes = {}, connectedNodesData = new Set(), visitedGroups = new Set(), initialGroup = null) {
      if (visitedNodes[selectedNode.id] || !selectedNode) {
        return connectedNodesData;
      }

      // 记录初始的 group
      if (initialGroup === null) {
        initialGroup = selectedNode.group;
      }

      visitedNodes[selectedNode.id] = true;

      // 在当前轮次中,复制一份 visitedGroups
      const currentVisitedGroups = new Set(visitedGroups);
      currentVisitedGroups.add(selectedNode.group);

      for (const link of inputGraph.links) {
        let directNode = null;

        if (link.source.id === selectedNode.id) {
          directNode = link.target;
        } else if (link.target.id === selectedNode.id) {
          directNode = link.source;
        }

        if (directNode && directNode.group !== initialGroup && !visitedGroups.has(directNode.group)) {
          connectedNodesData.add(directNode);
          getConnectedNodes(directNode, visitedNodes, connectedNodesData, currentVisitedGroups, initialGroup);
        }
      }

      // 转换为D3选择集
      return node.filter(function(d) {
        return connectedNodesData.has(d);
      }).nodes();
    }

    function getConnectedLinks(selectedNode, visitedNodes = {}, connectedLinksData = new Set(), visitedGroups = new Set(), initialGroup = null) {
      if (visitedNodes[selectedNode.id] || !selectedNode) {
        return connectedLinksData;
      }

      // 记录初始的 group
      if (initialGroup === null) {
        initialGroup = selectedNode.group;
      }

      visitedNodes[selectedNode.id] = true;

      // 在当前轮次中,复制一份 visitedGroups
      const currentVisitedGroups = new Set(visitedGroups);
      currentVisitedGroups.add(selectedNode.group);

      for (const link of inputGraph.links) {
        if (link.source.id === selectedNode.id || link.target.id === selectedNode.id) {
          let directNode = link.source.id === selectedNode.id ? link.target : link.source;

          if (directNode.group !== initialGroup && !visitedGroups.has(directNode.group)) {
            connectedLinksData.add(link);
            getConnectedLinks(directNode, visitedNodes, connectedLinksData, currentVisitedGroups, initialGroup);
          }
        }
      }

      // 转换为D3选择集
      return link.filter(function(d) {
        return connectedLinksData.has(d);
      }).nodes();
    }

    function mouseover(event, d) {
      // 获取鼠标的坐标
      var [x, y] = d3.pointer(event);
      tooltipDiv.transition()
          .duration(200)
          .style("opacity", .9);
      tooltipDiv.html("<span>" + d.name + "</span>") // 设置悬浮框的内容
          .style("left", (x + 20) + "px")
          .style("top", (y - 30) + "px");
    }

    function mouseout() {
      tooltipDiv.transition()
          .duration(500)
          .style("opacity", 0);
    }
  }
}
</script>

<style>

.tooltip {
  position: absolute;
  text-align: left;
  max-width: 300px;
  height: auto;
  padding: 5px;
  font: 12px sans-serif;
  background: #333;
  color: #fff;
  border: 0px;
  border-radius: 8px;
  pointer-events: none;
}

.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}

.legend text {
  display: block;
}

svg {
  cursor: move;
  overflow: hidden;
}

.legend {
  font-family: Arial, sans-serif;
  font-size: 14px;
}

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515

页面效果:

使用D3.js绘制知识图谱

另注:也可使用relation-graph开源项目绘制知识图谱

  • 项目描述:支持Vue和React的关联关系图谱组件,可以展示如组织机构图谱、股权架构图谱、集团关系图谱等知识图谱,可提供多种图谱布局,包括树状布局、中心布局、力学布局自动布局等。
  • 项目地址:https://github.com/seeksdream/relation-graph (opens new window)

relation-graph效果

# 6.9 Echarts动态渲染词云

项目简介:基于 wordcloud2.js 的 Apache ECharts 词云扩展

项目地址:https://github.com/ecomfe/echarts-wordcloud (opens new window)

注意事项:echarts-wordcloud现在有2.01.x两个版本,2.0对应echarts 5.x版本

$ npm install echarts
$ npm install echarts-wordcloud
1
2

项目结构:

echarts_version
 ├── lib
 │   ├── echarts-wordcloud.min.js
 │   └── echarts.min.js
 ├── logo.png
 └── wordCloud.html
1
2
3
4
5
6

wordCloud.html代码:

<html>
    <head>
        <meta charset="utf-8">
        <script src='./lib/echarts.min.js'></script>
        <script src='./lib/echarts-wordcloud.min.js'></script>
    </head>
    <body>
        <style>
            html, body, #main {
                width: 100%;
                height: 100%;
                margin: 0;
            }
        </style>
        <div id='main'></div>
        <script>
            var chart = echarts.init(document.getElementById('main'));

            var option = {
                tooltip: {},
                series: [ {
                    type: 'wordCloud',
                    gridSize: 2,
                    sizeRange: [12, 50],
                    rotationRange: [-90, 90],
                    shape: 'pentagon',
                    width: 600,
                    height: 400,
                    drawOutOfBound: true,
                    textStyle: {
                        color: function () {
                            return 'rgb(' + [
                                Math.round(Math.random() * 160),
                                Math.round(Math.random() * 160),
                                Math.round(Math.random() * 160)
                            ].join(',') + ')';
                        }
                    },
                    emphasis: {
                        textStyle: {
                            shadowBlur: 10,
                            shadowColor: '#333'
                        }
                    },
                    data: [
                        {
                            name: 'Sam S Club',
                            value: 10000,
                            textStyle: {
                                color: 'black'
                            },
                            emphasis: {
                                textStyle: {
                                    color: 'red'
                                }
                            }
                        },
                        {
                            name: 'Macys',
                            value: 6181
                        },
                        {
                            name: 'Amy Schumer',
                            value: 4386
                        },
                        {
                            name: 'Jurassic World',
                            value: 4055
                        },
                        {
                            name: 'Charter Communications',
                            value: 2467
                        },
                        {
                            name: 'Chick Fil A',
                            value: 2244
                        },
                        {
                            name: 'Planet Fitness',
                            value: 1898
                        },
                        {
                            name: 'Pitch Perfect',
                            value: 1484
                        },
                        {
                            name: 'Express',
                            value: 1112
                        },
                        {
                            name: 'Home',
                            value: 965
                        },
                        {
                            name: 'Johnny Depp',
                            value: 847
                        },
                        {
                            name: 'Lena Dunham',
                            value: 582
                        },
                        {
                            name: 'Lewis Hamilton',
                            value: 555
                        },
                        {
                            name: 'KXAN',
                            value: 550
                        },
                        {
                            name: 'Mary Ellen Mark',
                            value: 462
                        },
                        {
                            name: 'Farrah Abraham',
                            value: 366
                        },
                        {
                            name: 'Rita Ora',
                            value: 360
                        },
                        {
                            name: 'Serena Williams',
                            value: 282
                        },
                        {
                            name: 'NCAA baseball tournament',
                            value: 273
                        },
                        {
                            name: 'Point Break',
                            value: 265
                        }
                    ]
                } ]
            };

            chart.setOption(option);

            window.onresize = chart.resize;
        </script>
    </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

渲染效果:

# 7. 参考资料

[1] fantastic-admin代码规范 from Gitee (opens new window)

[2] vscode怎么设置打开新文件的时候不关闭没有修改过的旧文件?from segmentfault (opens new window)

[3] vscode 文件标签栏多行显示 from CSDN (opens new window)

[4] npm run serve和npm run dev的区别 from 腾讯云 (opens new window)

[5] npm install 安装出错时尝试过的方法 from CSDN (opens new window)

[6] npm换源与恢复官方源 from 思考生活 (opens new window)

[7] Vue开发环境中修改端口号 from segmentfault (opens new window)

[8] vue项目打包-上传-部署到linux的nginx服务器上 from CSDN (opens new window)

[9] vue-cli如何打包和图片上传丢失问题 from CSDN (opens new window)

[10] VUE项目打包(解决背景图片不显示问题) from CSDN (opens new window)

[11] vue3.0以上关于打包后出现空白页和路由不起作用 from CSDN (opens new window)

[12] nativefier使任何网页成为桌面应用程序 from Github (opens new window)

[13] Nginx Windows详细安装部署教程 from 博客园 (opens new window)

[14] vue 登录页背景-粒子特效(Vue-Particles)from 掘金 (opens new window)

[15] Vue页面上实时显示当前时间,每秒更新 from cnblogs (opens new window)

[16] vue实时显示当前时间且转化为“yyyy-MM-dd hh:mm:ss”格式 (opens new window)

[17] 基于element的区间选择组件 from segmentfault (opens new window)

[18] ECharts实现地图散点图 from efe (opens new window)

[19] ECharts实现的中国地图散点图示例 from Github (opens new window)

[20] Echarts中国地图,支持下钻 from Github (opens new window)

[21] js校验大于等于0的正数和正整数 from 简书 (opens new window)

[22] js的加减乘除 from 掘金 (opens new window)

[23] vue 解析md文件 并渲染样式 from CSDN (opens new window)

[24] Module build failed (from ./node_modules/html-loader/dist/cjs.js): TypeError: this.getOptions is not a function报错 from CSDN (opens new window)

[25] 解决vue封装的echarts组件多次调用出现id重复问题 fromCSDN (opens new window)

[26] vue.js 父组件主动获取子组件的数据和方法、子组件主动获取父组件的数据和方法 from segmentfault (opens new window)

[27] Vue生命周期中mounted和created的区别 from CSDN (opens new window)

[28] vue+element-ui 回到顶部组件backTop from CSDN (opens new window)

[29] 将数据存入vuex中以及从vuex中取出数据 from CSDN (opens new window)

[30] 解决vuex页面刷新导致数据丢失问题 from segmentfault (opens new window)

[31] show-overflow-tooltip显示的宽度设置、.el-tooltip__popper无效问题解决 from CSDN (opens new window)

[32] vue项目,父组件每次点击按钮,重新加载子组件 from CSDN (opens new window)

[33] Vue2.0的三种常用传值方式、父传子、子传父、非父子组件传值 from CSDN (opens new window)

[34] JS生成一个最近一周日期的数组 from CSDN (opens new window)

[35] vue md5加密用法 from 掘金 (opens new window)

[36] Angular11(Cli) 在打包 ng build 后的常见问题处理方案 from CSDN (opens new window)

[37] 在 Nginx 中部署 Angular 应用 - 解决路由冲突 404 问题 from CSDN (opens new window)

[38] windows系统下如何安装多版本node from 稀土掘金 (opens new window)

[39] nvm安装,nvm use 一直报错exit status 1 或 exit status 145 问题 from 前端网 (opens new window)

[40] npm install 报错 gyp info it worked if it ends with ok npm ERR! gyp verb cli from CSDN (opens new window)

[41] [问题]项目无法启动,error:0308010C:digital envelope routines::unsupported from Github Issues (opens new window)

[42] node-sass install ERROR with Node 18 version from stackoverflow (opens new window)

[43] Mac Monterey下安装Python2 from 依我愚见+ (opens new window)

[44] Mac(m1)下npm install报错解决方案 from 博客园 (opens new window)

[45] 如何解决 npm 安装依赖报错 ERESOLVE unable to resolve dependency tree from 稀土掘金 (opens new window)

[46] vs code中如何运行html静态文件 from CSDN (opens new window)

[47] 使用 docker + verdaccio 搭建私有npm仓库 from 稀土掘金 (opens new window)

[48] Chrome插件开发 from herohql521 (opens new window)

[49] chrome插件之通信(v3)from 稀土掘金 (opens new window)

[50] Chrome插件(扩展)开发全攻略 from deepmind (opens new window)

[51] ECharts中的 formatter中的a,b,c,d等参数的意义 from CSDN (opens new window)

[52] 关于如何设置echart图例(legend)的位置 from CSDN (opens new window)

[53] echarts标题(title)配置 from CSDN (opens new window)

[54] 纯前端实现词云展示+附微博热搜词云Demo代码 from 稀土掘金 (opens new window)

[55] 基于 wordcloud2.js 的 Apache ECharts 词云扩展 from Github (opens new window)

Last Updated: 11/25/2023, 3:42:40 PM