# 2020年08月技术日常
# 2020/08/30 周日
# npm run 运行多条命令 && 不生效的问题
在项目中除了默认的 webpack-dev-server 外,我们还想同时运行mock接口的node服务
// npm run 同时执行下面的两条命令
// webpack-dev-server --config webpack.dev.js
// nodemon ./mock/index.js
一般 & 表示并列执行,&& 表示两条命令顺序执行。使用 && 的形式设置对应的script,如下
"scripts": {
"server": "nodemon ./mock/index.js && webpack-dev-server --config webpack.dev.js"
}
但实际运行 npm run server 时,只运行了第一条命令,我的是mac,不知道是不是node版本的问题。网上找了下, && 存在兼容性问题。建议使用 concurrently 来代替,concurrently 跨平台兼容
// 先安装 concurrently
// npm install concurrently --save
// 修改 package.json 如下
"server": "concurrently \"nodemon ./mock/index.js\" \"webpack-dev-server --config webpack.dev.js\""
这样就可以了,运行效果如下
注意:&& 是按顺序执行多条命令, concurrently 是并列执行多条命令,对于跨平台的顺序执行,可以使用 npm-run-all
参考:npm并行&串行执行多个scripts命令 (opens new window)
# mac设置vscode默认打开的浏览器
vscode中,安装 Live Server 插件后,在html文件里,右键 open with Live Server,可以将网页使用http服务在浏览器里面打开。
我电脑里面默认打开的是safari,但我一般习惯用Chrome,这个默认浏览器的设置需要在mac系统里设置
点击屏幕左上角苹果标志 => 系统偏好设置 => 通用 => 默认网页浏览器 选择 Google Chrome 即可
# 2020/08/24 周一
# 怎么在老项目中加入eslint规则,并可以保存后自动fix
在 "vue-cli构建项目时选择不同的eslint规则会有什么区别?" 中,我们有了解到,不同的eslint配置项,会对应不同的npm包,eslintConfig 中对应的 extends 也不一样。我们只要找出某个配置特有的npm包,以及配置,就可以在旧项目中,引入对应的eslint配置,结合vscode eslint插件就可以做到保存后自动fix
- 新建一个目录eslint-fix-test,cd到该目录,使用 npm init -y 创建一个新的package.json
- 我们把 ESLint + Prettier 的package.json配置拷贝到这个package.json中
// ESLint + Prettier package.json配置
"devDependencies": {
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^1.19.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"@vue/prettier"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
}
- npm install 安装依赖
- 在该目录下写一个test.vue文件,或test.js文件,特意写的很随意,看eslint是否报错
一般情况下,npm install 后,如果vscode eslint插件配置正确,会弹出如下弹窗,提示是否允许eslint修复
注意:
- 如果没有安装配置好vscode的eslint插件,请先安装,可以参考我之前的笔记: 2020 vscode配置eslint保存后自动fix (opens new window)
- 如果没有生效,可以尝试新开一个vscode窗口,直接打开eslint-fix-test目录,有时候目录层级深了,会不起作用
- 如果还是没生效,可以尝试彻底关闭 vscode,再重新打开
- 有时候刚打开项目,eslint插件可能需要时间加载,等个几秒钟才会工作
下面是我测试时,eslint插件工作正常的示例
保存后会自动fix,由于两个变量没使用还是会报警告,我用console.log打印了下,就没warning了
综上,只要你知道某个eslint配置所需的npm包及eslintConfig.extends配置,那么就可以在项目中任意使用eslint了,完整测试demo可以从github 下载 eslint-fix-test | github (opens new window)
# vue-cli构建项目时选择不同的eslint规则会有什么区别?
当我们使用 vue create xxx 时,会让选择是否需要某个模块,如下图
如果有使用空格选择 Linter / Formatter,那么后面的流程会让你选择一种eslint规则
? Pick a linter / formatter config: (Use arrow keys)
❯ ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier
加上不使用Linter / Formatter,总共有5中情况,我们都逐一试试,看生成的package.json有什么不同
注意除了eslint规则选择不一样外,其他都一样。vue-cli 版本 4.5.0,vue 版本 2.x
// 统一选择 lint on save,配置放到package.json,不单独生成elsint配置文件
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
# 不使用eslint时
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"vue-template-compiler": "^2.6.11"
},
# ESLint with error prevention only
// 注释掉除eslint功能之外的配置
"scripts": {
// "serve": "vue-cli-service serve",
// "build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"devDependencies": {
// "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
// "@vue/cli-plugin-router": "~4.5.0",
// "@vue/cli-plugin-vuex": "~4.5.0",
// "@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
// "less": "^3.0.4",
// "less-loader": "^5.0.0",
// "vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
# ESLint + Airbnb config
"devDependencies": {
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/eslint-config-airbnb": "^5.0.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-vue": "^6.2.2",
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/airbnb"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
多了一个.editorconfig
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
# ESLint + Standard config
"devDependencies": {
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
多了一个.editorconfig
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
# ESLint + Prettier
"devDependencies": {
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^1.19.1",
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"@vue/prettier"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
我们使用上面相同的配置,只是不选择将eslint配置放到paackage.json,而是选择 In dedicated config files 使用专门的配置文件,他会额外生成一个 .eslintrc.js 存放对应的eslint配置
// .eslintrc.js
module.exports = {
root: true,
env: {
node: true
},
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint"
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
}
};
# ESLint配置配置对比总结
// 1. ESLint with error prevention only
// - devDependencies
"@vue/cli-plugin-eslint": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
// - eslintConfig
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
// 2. ESLint + Airbnb config
// - devDependencies
"@vue/cli-plugin-eslint": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"@vue/eslint-config-airbnb": "^5.0.2",
"eslint-plugin-import": "^2.20.2",
// - eslintConfig
"extends": [
"plugin:vue/essential",
"@vue/airbnb"
],
// 3. ESLint + Standard config
// - devDependencies
"@vue/cli-plugin-eslint": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"@vue/eslint-config-standard": "^5.1.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
// - eslintConfig
"extends": [
"plugin:vue/essential",
"@vue/standard"
],
// 4. ESLint + Prettier
// - devDependencies
"@vue/cli-plugin-eslint": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"@vue/eslint-config-prettier": "^6.0.0",
"eslint-plugin-prettier": "^3.1.3",
"prettier": "^1.19.1",
// - eslintConfig
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"@vue/prettier"
],
# 怎么用vue写一个组件库,类似element
下面通过一个简单的示例来看怎么写一个vue组件库
# 1.组件库目录结构
以element为例,下面是一个简单的目录结构
├── examples # 用于测试组件,demo展示(可以直接是一个完整的vue项目)
├── packages # 用于存放编写的组件
│ └── my-button # 封装的组件,具体写法参见element源码
│ ├── src
│ │ └── main.vue # 单文件组件,组件
│ └── index.js # install方法,用于Vue.use()引入单个组件
└── src
└── index.js # 入口文件
# 2.导出组件库
我们写好组件后,主要问题是要怎么导出(export)供其他项目使用,一般在 src/index.js 里进行处理
// src/index.js 示例,主要是引入(import)组件再导出(export),包括Vue.use所需的 install函数处理
import MyButton from "../packages/my-button/index.js";
const components = [MyButton];
// Vue.use() 一次性安装所有组件
const install = function(Vue) {
if (install.installed) return;
components.forEach(component => Vue.use(component));
// 如果没有在src/index.js里实现install方法,就需要使用下面的
// components.forEach(component => Vue.component(component.name, component))
};
// 直接给浏览器或 AMD loader 使用
if (typeof window !== undefined && window.Vue) {
install(window.Vue);
}
export default {
install, // 用于ES modules,import Vue 后直接使用 Vue.use()
MyButton // 支持解构赋值按需引入单个组件
};
# 3.本地测试组件
假设我们这个组件命名为vue-chart,那可以在 examples/src/mian.js 直接使用
// examples/src/mian.js
// 引入所有组件
import VueChart from '../../src/index.js'
Vue.use(VueChart)
// 按需引入单个组件
import { MyButton } from '../../src/index.js'
Vue.use(MyButton)
引入后,我们在 App.vue里直接使用该组件
<!-- App.vue -->
<template>
<div id="app">
<my-button id="btn">测试按钮</my-button>
</div>
</template>
这里可能涉及到 examples 目录 npm run serve 时,提示 eslint 错误
因为 examples 是一个独立的vue项目,引入了外部 packages 目录的文件,如果 packages 下组件不符合eslint规则就需要在项目根目录的package.json里配置eslint规则了,与examples项目的eslint规则保持一致。参考我之前的笔记:"怎么在老项目中加入eslint规则,并可以保存后自动fix"
这样使用eslint处理 packages 目录下的代码吗,再运行 examples 目录下的 npm run serve 就可以正常加载组件了
# 4.打包构建umd
上面我们本地测试了,但他不能直接在普通网页中通过引入某个js文件来使用,这就需要使用Vue CLI来打包构建了
使用 vue-cli 打包成 lib,参考文档 构建目标 | Vue CLI (opens new window)
由于我们的项目结构不是通过vue-cli生成的,所以就算使用npm install @vue/cli --save
,安装 @vue/cli,也无法使用vue-cli-service服务,因为@vue/cli这个包是一个用于生成脚手架项目的。你没有用它生成项目,如果不安装对应的npm包是无法使用vue-cli-servie的,我们直接从vue create 生成的项目中把 devDependencies、dependencies,配置移动到根目录的package.json里,再npm install
// dependencies
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
// devDependencies
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"vue-template-compiler": "^2.6.11"
// browserslist
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
新增一个lib打包命令
"scripts": {
"build:lib": "vue-cli-service build --mode lib --target lib --dest lib src/index.js"
}
理论上 npm run build:lib 会在当前目录下创建lib目录,且包含4个文件
- lib/myLib.common.js:一个给打包器用的 CommonJS 包 (不幸的是,webpack 目前还并没有支持 ES modules 输出格式的包)
- lib/myLib.umd.js:一个直接给浏览器或 AMD loader 使用的 UMD 包
- lib/myLib.umd.min.js:压缩后的 UMD 构建版本
- lib/myLib.css:提取出来的 CSS 文件 (可以通过在 vue.config.js 中设置 css: { extract: false } 强制内联)
实际我这里打包后没有生成css文件
一般将打包后的文件 xx.umd.js 引入到项目中就可以使用了,来看看示例
<!-- docs/index.html -->
<body>
<div id="app">
<my-button>我的按钮</my-button>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="../lib/vue-chart.umd.js"></script>
<script>
const app = new Vue({
el: '#app'
})
</script>
</body>
vue项目中引用
// examples/src/main.js
import VueChart from "../../lib/vue-chart.umd.js";
Vue.use(VueChart);
效果如下
# 5.上传npm包
我们可以把包弄成npm包,在其他vue项目中,可以直接引入,将 package.json 的 main.js 设置为umd模块地址,并加上unpkg参数,用于cdn直接引入
// package.json
"name": "@guoqzuo/vue-chart", // 将项目名称改为加上自己作用域的包
"version": "0.0.2", // 设置项目版本,注意每次修改版本,package-lock.json的版本也要改
// 指定npm包入口,当我们import某个npm包时,导入的文件就是这个main指定的文件
"main": "lib/vue-chart.umd.min.js"
// cdn相关
"unpkg": "lib/vue-chart.umd.min.js",
关于unpkg
修改好后,我们来发布这个npm包,注意:需要先有对应的npm账号,我的npm账号是 guoqzuo,这里在项目根目录新建一个publish.sh脚本来发布
#!/usr/bin/env bash
npm config get registry # 检查仓库镜像库
npm config set registry=http://registry.npmjs.org
echo '请进行登录相关操作:'
npm login # 登陆
# npm login --scope=@guoqzuo # 设置登录作用域
echo "-------publishing-------"
npm publish --access public # 发布
npm config set registry=https://registry.npm.taobao.org # 设置为淘宝镜像
echo "发布完成"
exit
一般新建的publish.sh是没有执行权限的,使用 chmod +x publish.sh
添加可执行权限,再 ./publish.sh
执行发布,发布过程中,需要登录npm账号、密码等即可
我们这里examples目录也跟着发布上传了,可以加参数来限定上传到npm包的文件。细节方面后面在优化。我们可以看到npm官网有我们刚上传的 @guoqzuo/vue-chart (opens new window) 了
测试npm包,在vue项目中先安装
# 这里建议加@latest,因为有时候发布后有延时
# 直接npm i @guoqzuo/vue-chart 后可能还是原来的旧版本
npm i @guoqzuo/vue-chart@latest
安装完成后,再在 main.js 里引入,npm run serve 后可正常渲染
// examples/src/main.js
import VueChart from "@guoqzuo/vue-chart";
Vue.use(VueChart);
另外,我们再测试下 unpkg cdn是否生效
<!-- docs/index.html -->
<body>
<div id="app">
<my-button>我的按钮</my-button>
</div>
<script src="https://unpkg.com/vue"></script>
<!-- <script src="../lib/vue-chart.umd.js"></script> -->
<script src="https://unpkg.com/@guoqzuo/vue-chart@latest/lib/vue-chart.umd.min.js"></script>
<script>
const app = new Vue({
el: '#app'
})
</script>
</body>
测试可正常运行,以上完成了一个基本vue组件库的开发流程。后面再慢慢优化,完整代码已上传github,参见 zuoxiaobai/vue-chart: vue echarts library (opens new window)
参考:
- 如何用vue写一个组件库 (opens new window)
- 写一个vue组件库_跟着element学习写组件 (opens new window)
- Vue Loader (opens new window)
- package.json 非官方字段集合 - 前端小站 - SegmentFault 思否 (opens new window)
# 2020/08/21 周五
# 怎么写出类似element官网那样可以实时看运行效果的文档
最近想做一个文档,把项目中之前实现过的图表组件整合成一个文档。有两个目的:
- 让产品/UI在做新功能时,直接从里面挑。不要弄一些不好实现的,需要很大工作量的图表。(虽然说了让产品看echarts官网,但基本不看)
- 团队技术沉淀,做了这么多图表,各种属性的配置没有说明文档,每次都是自己调参数。如果每个图表都有说明、笔记,那后面维护的人就可以快速上手了。
对于UI组件、echarts图表相关文档来说,能够实时看具体显示效果是必须的。它是将文档和demo合并在一起,这样更有说服力。那怎么写出类似的文档呢?
# docsify
markdown语法,可嵌入vue代码。基础用法可以参考文档: docsify (opens new window),这里介绍下嵌入vue代码需要注意的问题
由于docsify入口是index.html,本身不支持ES modules,假设vue示例代码中,需要引入echarts模块,是无法import直接运行的。需要通过在index.html引入js来引入组件。有一个比较好的示例,可以参考 ve-charts (opens new window), 可以把源码clone下来跑一跑。通过 npm run build:lib
打包生成 umd 形式的js文件,直接引入即可。
<!-- index.html部分代码 -->
<script src="//cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<!-- vuep docsify插件,用于实时编辑、显示代码 -->
<script src="//cdn.jsdelivr.net/npm/vuep/dist/vuep.min.js"></script>
<!-- vuep默认是CommonJS规范,需要使用 module.exports,加babel后可以使用ES Modules的export default -->
<script src="https://unpkg.com/babel-standalone/babel.min.js"></script>
<script src="//unpkg.com/echarts@latest/dist/echarts.min.js"></script>
<!-- ve-echarts组件库输出的umd形式js文件,只载入这个js即可完整引入ve-echarts组件库 -->
<script src="./lib/ve-charts.umd.min.js"></script>
README.md
<!-- README.md 里面嵌入vue代码 -->
<vuep template="#basicLine" :options="{ theme: 'vue', lineNumbers: false }"></vuep>
<script v-pre type="text/x-template" id="basicLine">
<template>
<ve-line-chart :data="chartData" />
</template>
<script>
export default {
created () {
this.chartData = {
dimensions: {
name: 'Week',
data: ['Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fir.', 'Sat.', 'Sun.']
},
measures: [{
name: 'Vue',
data: [30, 40, 35, 50, 49, 70, 90]
}]
}
}
}
</script>
效果如下图
# vuepress
vuepress (opens new window) 是一个静态站点生成器,用于vue官网文档生成。可以内嵌vue代码,Using Vue in Markdown (opens new window)
# element官网源码
ElementUI官网 examples | github (opens new window)
以 alert文档源码 (opens new window) 为例
## Alert 警告
用于页面中展示重要的提示信息。
### 基本用法
页面中的非浮层元素,不会自动消失。
:::demo Alert 组件提供四种主题,由`type`属性指定,默认值为`info`。
<template>
<el-alert
title="成功提示的文案"
type="success">
</el-alert>
<el-alert
title="消息提示的文案"
type="info">
</el-alert>
<el-alert
title="警告提示的文案"
type="warning">
</el-alert>
<el-alert
title="错误提示的文案"
type="error">
</el-alert>
</template>
:::
### 主题
渲染效果如下
它是自己写的一套构建程序,将tpl转vue,md解析时,内嵌vue demo会自动渲染代码
// 将 官网.tpl单文件组件转.vue
// /examples/pages/template/component.tpl 就是element组件文档的入口
// https://github.com/ElemeFE/element/blob/dev/build/bin/i18n.js
'use strict';
var fs = require('fs');
var path = require('path');
var langConfig = require('../../examples/i18n/page.json');
langConfig.forEach(lang => {
try {
fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));
} catch (e) {
fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));
}
Object.keys(lang.pages).forEach(page => {
var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);
var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);
var content = fs.readFileSync(templatePath, 'utf8');
var pairs = lang.pages[page];
Object.keys(pairs).forEach(key => {
content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);
});
fs.writeFileSync(outputPath, content);
});
});
/examples/pages/template/component.tpl 部分代码
<el-scrollbar class="page-component__nav">
<side-nav :data="navsData[lang]" :base="`/${ lang }/component`"></side-nav>
</el-scrollbar>
<div class="page-component__content">
<!-- 像 alert、button 文档切换,就是切的这里的路由 -->
<router-view class="content"></router-view>
<footer-nav></footer-nav>
</div>
对应的路由代码
// element/examples/route.config.js为alert等文档添加路由部分代码
function addRoute(page, lang, index) {
const component = page.path === '/changelog'
? load(lang, 'changelog')
: loadDocs(lang, page.path); // 根据path载入对应的docs/zh-CN/xx.md
let child = {
path: page.path.slice(1),
meta: {
title: page.title || page.name,
description: page.description,
lang
},
name: 'component-' + lang + (page.title || page.name),
component: component.default || component
};
route[index].children.push(child);
}
这里涉及webpack按需加载,构建打包相关,比如怎么将md转换vue,待后续研究
# 2020/08/14 周五
# el-tabs跳转之前先弹窗筛选后再跳转
增加before-leave钩子,它除了支持true,false外,还支持promise resolve和reject,可以将调用js弹窗组件函数改为promise,如果非提交表单关闭弹窗就reject,否则resove表单数据。调用该函数时使用try catch可以很好的处理筛选组件弹窗确定后再跳转到对应tab的逻辑。
# FileReader实例的readAsDataURL与createObjectURL区别
都是读取blob数据, FileReader实例的readAsDataURL下载文件会有限制,超过一定大小会出错,而window.URL.createObjectURL却没有限制
readAsDataURL URI base64数据 FileReader.readAsDataURL() | MDN (opens new window)
createObjectURL URL url URL.createObjectURL() | MDN (opens new window)
FileReader.readAsDataURL 读取文件之后是 base64 编码的字符串,这个是不能直接作为 src 使用的,要直接使用还应当拼接响应的 MIME Type 前缀,比如 data:audio/ogg; 这是 .ogg 格式的前缀,具体是什么前缀取决于你上传文件的扩展类型了。
其实这里没必要非使用 FileReader 来完成这个需求,读一些小的文件还可以,读大的文件其实不是很好。不如直接使用 URL.createObjectURL() 来创建一个 DOMString,然后直接使用这个 DOMString 即可,不过不要忘记使用完之后通过 URL.revokeObjectURL()方法来释放。
参考:FileReader readAsDataURL读取视频文件 一直预览失败 怎么解决? (opens new window)
# 2020/08/05 周三
# 前端代码规范风格指南
功能组件、目录结构
// 新建文件夹,命名以小写字母开头,驼峰命名
- moduleA // 模块A目录
- comps // 组件目录
- CustomerRefuse.vue // 单文件组件命名规则,参考vue风格指南
// 如果组件内容较多,创建一个文件夹
// 命名以npm包命名规则一致,全小写、-分隔,建议不超过3个单词
- no-permission
- src // 其他资源目录,参考Element组件源码
- index.vue // 入口或者使用index.js 方便 Vue.use 引入
- index.vue // 模块A入口
Vue、HTML、JS、CSS编码风格
- Vue风格指南 (opens new window)
- Bootstrap 编码规范: 编写一致、灵活和可持续的 HTML 和 CSS 代码的规范。 (opens new window)
- Google JavaScript Style Guide (opens new window)
- Airbnb JS风格指南 (opens new window)
# 2020/08/04 周二
# 用node实现发送包含echarts图表的邮件
在之前我用node研究过怎么收发邮件,但没有研究过邮件里面是否能包含图表,这次来研究下
首先,我们来看看怎么发邮件
# 准备工作:配置发件人邮箱
我们发送邮件,首先需要有一个邮箱作为发件人,以QQ邮箱为例,我们发邮件使用 916707888@qq.com
作为发件人,我们需要使用程序自动发邮件,所以需要配置发邮件的服务,开启发邮件的POP3/SMTP服务,得到授权码在操作,下面来看看具体过程
登录到qq邮箱,进入管理页面,在设置 - POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务位置,开启POP3/SMTP服务
开启服务后,会得到POP3和IMAP授权码,一个用来收邮件一个用来发邮件,保存好
gmail邮箱配置
# 使用nodemailer来发送邮件
使用 nodemailer (opens new window) 来发送邮件,首先开一个koa demo
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()
/**
* @description 使用nodemailer发送邮件
* @example
* POST http://127.0.0.1:9000/sendEmail
*/
router.get('/sendEmail', async ctx => {
try {
let sendEmail = require('./sendEmail/index.js')
await sendEmail()
ctx.body = {
msg: '成功'
}
} catch (e) {
ctx.body = {
msg: e.message
}
}
})
app.use(router.routes())
app.listen('9000', () => {
console.log('server start on 9000 port')
})
发邮件核心代码 sendEmail/index.js
const nodemailer = require('nodemailer');
function sendEmail() {
return new Promise((resolve, rejected) => {
// create reusable transport method (opens pool of SMTP connections)
let smtpTransport = nodemailer.createTransport({
host: "smtp.qq.com", //qq smtp服务器地址, 如果是其他邮箱需要修改为对应的服务器
secureConnection: false, //是否使用安全连接,对https协议的
port: 465, //qq邮件服务所占用的端口
auth: {
user: "916707888@qq.com", //开启SMTP的邮箱,有用发送邮件
pass: "rorvinuqemsybccc" // qq POP3/SMTP授权码,如果是gmail,直接填密码
}
});
// setup e-mail data with unicode symbols
let mailOptions = {
from: "guoqzuo <i@zuoguoqing.com>", // sender address
to: "i@zuoguoqing.com, zuoguoqing@aliyun.com", // list of receivers
subject: "邮箱验证码", // Subject line
text: `Hello,您的验证码是 1212323`, // plaintext body
// html body
html: `
Hello,您的验证码是
<img src="https://iknow-pic.cdn.bcebos.com/adaf2edda3cc7cd96b1584973701213fb80e9140?x-bce-process=image/resize,m_lfit,w_600,h_800,limit_1">
<img src="http://www.zuo11.com/images/blog/web/qrcode.jpg">
<div>
<span style="color:red;">1243</span>
<div style="margin:50px;border:1px solid #ccc;height: 100px;widht:100px;">1243</div>
</div>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script crossorigin="anonymous" integrity="sha384-i+fXrQ+G3+h2478EWpSpIXivtKbbze+0SNOXJGizkAp6DVG/m2fE6hiWeDwskVvp" src="https://lib.baomitu.com/echarts/4.7.0/echarts.js"></script>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data:['销量']
},
xAxis: {
data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
`
}
// send mail with defined transport object
smtpTransport.sendMail(mailOptions, function (error, response) {
if (error) {
rejected(error);
} else {
console.log("Message sent: " + response.message);
// 发送成功
console.log('邮件发送成功');
resolve('发送成功');
}
// if you don't want to use this transport object anymore, uncomment following line
smtpTransport.close(); // shut down the connection pool, no more messages
});
});
}
module.exports = sendEmail
上面发送的是html富文本,发送后,可以看到,简单的css样式,图片是可以发送的,但js是无法执行,如下图
# 使用接口生成echarts图片
在上面的例子中,我们知道无法执行js,也就是无法直接在邮箱使用canvas绘图,那么我们可以换个思路,通过一个src,get请求在后端生成echarts图片
怎么在后端用node把echart渲染出来并生成图片呢?这里用到了 node-charts
模块,它使用 puppeteer 来进行渲染截图
/**
* @description 根据接口生成echarts图片
* @example
* GET http://127.0.0.1:9000/png?type=a
* GET http://127.0.0.1:9000/png?type=b
*/
router.get('/png', async ctx => {
try {
let getEchartsPng = require('./echartsPng/index.js')
let optionsA = require('./echartsPng/optionsA.js')
let optionsB = require('./echartsPng/optionsB.js')
let data = await getEchartsPng(ctx.query.type === 'a' ? optionsA : optionsB)
ctx.set({
'content-type': 'image/png'
})
ctx.body = data
} catch (e) {
console.log(e)
ctx.body = {
msg: e.message
}
}
})
渲染相关逻辑,这样通过接口就可以直接访问一张echart的图片了
const NodeCharts = require('node-charts');
function getEchartsPng(options) {
return new Promise((resolve, reject) => {
let nc = new NodeCharts();
//监听全局异常事件
nc.on('error', (err) => {
reject(err)
});
nc.render(options, (err, data) => {
if (err) {
reject(err)
}
resolve(data)
}, {
type: 'echarts'
})
})
}
module.exports = getEchartsPng
请求截图
完成demo,参见github: 用邮件发送echarts图表 (opens new window)