Ekko_

Ekko_ 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

一个前端菜鸟,级别为前端中的负级

个人动态

Ekko_ 发布了文章 · 11月20日

Vue3.0初体验 - 新功能以及相关库使用

前言

vue3.0从发布到现在已经有几个月了,基本上大家都处于一个试玩阶段。但搬砖的厂里有了新项目,领导提出新项目用vue3.0 + ts踩坑的想法,那么vue3.0到底怎么玩,最近也稍微搞了下,下面快速和铁汁们分享一波基础的使用。

看完文章,你会学习到:

  1. vue3的使用安装
  2. vue3的新特性
  3. vuex,vue-router的升级和使用

老样子,下面我们直接开冲!
image

体验Vue3的方式

  • vue-cli
官方指定cli工具,要更新最新版本。这个比较稳定,建议刚开始使用这个
  // 新版vue-cli会多出一个创建vue3的项目、选择vue3之后、其他的配置就看大家们的喜好了
  npm install -g @vue/cli
  vue create vue3-project
  cd vue3-project
  vue add vue-next
  npm run serve
  • webpack
这个是vue-cli还没开始支持时候,vue官网自己搞得一套webpack项目配置,直接clone
  git clone https://github.com/vuejs/vue-next-webpack-preview.git 01-vue3-webpack
  cd 01-vue3-webpack
  npm install 
  npm run dev
  • vite
这个就比较有意思了,这个是尤大大最近着重开发的一个工具,意在替代webpack打包,核心是利用浏览器现在已经支持import,遇到import会使用http请求去加载文件,在vite中使用koa拦截这些请求,进行分类以及预编译,加载各个模块,在build时候使用rollup进行打包,节省了大部分时间,实现复杂项目秒开
  npm install -g create-vite-app
  create-vite-app vue3-vite
  cd vue3-vite
  npm install
  npm run dev

ps: 铁汁们在使用的时候注意自己的node版本,使用vite时候需要高版本的node。

Vue3新特性

  • Composition API体验
包含setup、ref、computed等。由于reactive,compiler-sfc, complier-dom等核心模块的抽离,可以更加自由的在项目中引用使用,使函数式编程可以发挥更大的作用

setup

Vue3.0的重要新函数,熟悉React的同学们肯定经常用Hooks,其实我个人感觉这个和Hooks其实很像,当然我也不清楚尤大大是不是借鉴的React(/手动狗头),不过函数式编程肯定是爽的。

setup是个独立的函数,内部可以使用Vue的所有模块,包括了生命周期、响应式、computed、watch等,最后返回一个对象,可以在template中直接调用。

reactive、ref

看名字就知道是vue的三大件之一响应式的模块,这次做了很大革新,使用了proxy代替以前的defineprototype,性能上提升很多,支持了Array的监听,并且单独抽离了出来。
使用方式是传入一个对象,返回一个proxy代理监听过的对象,代理过的数据都是响应式的。

computed、watch
这次computed和watch都拆分成了独立的模块,在使用的时候按需引入进来使用方式也有了丢丢改变

好了,说了那么多,光说不练假把式,我们写个🌰来看看:

<template>
  <div>
    <p>count: {{state.count}}</p>
    <p>doubleCount: {{doubleCount}}</p>
    <p>number: {{number}}</p>
    <div><button @click="add">add</button></div>
  </div>
</template>
<script>
    import { reactive, computed, ref } from 'vue'

    export default {
      name: 'App',
      setup () {
        const state = reactive({
          count: 1
        })

        const number = ref(0)

        const doubleCount = computed(() => state.count * 2)

        function add () {
          state.count++
          number.value += 10
        }

        return { state, doubleCount, add, number }
      }
    }
</script>
  • Fragment

这个我个人认为还是很爽的,支持多根节点,不用特意在外面故意套一层无用DOM,虽然一些纠错工具依然会标红就是了....

<template>
  <h3>体验一把Fragment</h3>
  <h3>可以有多个根节点</h3>
</template>

<script>
    ...
</script>
  • Teleport

这个和react的传送门的概念差不多,也是创建一个DOM插入到根节点。

<template>
  <div>
    <h3>类react传送门Teleport</h3>
    <p><button @click="isOpen = !isOpen">打开/关闭弹窗</button></p>
    <Teleport to="#app">
      <div v-if="isOpen">一个弹窗!!!!!!</div>
    </Teleport>
  </div>
</template>

<script>
import { reactive, computed, ref } from 'vue'

export default {
  name: 'App',
  setup () {
    const isOpen = ref(false)
    return { isOpen }
  }
}
</script>

Vue3配套的库

虽然上面说了Vue3的新特性,咱们开发项目肯定不能只用框架使劲怼,还得让配套的库跟上才好用,下面介绍下vuex和vue-router的使用。

  • 一键升级vuex和vue-router
vue add vue-next
  • vue-router 4.x 单独安装&使用

    • 安装方式
    npm install vue-router@next
    • 使用方式
    // route.js
    // route注册
    import { createRouter, createWebHistory } from 'vue-router'
    
    const routes = [
      // ...这里老样子
    ]
    
    const router = createRouter({
      history: createWebHistory(process.env.BASE_URL),
      routes
    })
    
    export default router
    
    // Page页面
    // template中使用 组件相同 Link Router-view组件
    <template>
        <link to="xxx" />
        <router-view /></router-view>
    </template>
    
    // script中方法调用
    
    <script>
        import { useRouter } from 'vue-router'
        
        export default {
          name: 'App',
          setup () {
            const router = useRouter()
            router.push({path: '', query: {}})
            // ...
          }
        }
    </script>
  • vuex 4.x 单独安装&使用

    • 安装方式
    npm install vuex@next
    • 使用方式
       // store.js
       // vuex注册
      import { createStore } from 'vuex'
    
      export default createStore({
        state: {
            userName: 'xiaoming'
        },
        mutations: {},
        actions: {},
        getters: {},
        modules: {}
      })
        
      // Page页面
      // 获取store
      <script>
        import { useStore } from 'vuex'
        
        export default {
          name: 'App',
          setup () {
            store = userStore()
            
            const userName = store.state.userName
            // ...
          }
        }
      </script>
  • vue和相关库在main文件中注册使用
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index.js'
import store from './store/index.js'

const app = createApp(App)

// 注册路由
app.use(router)

// 注册vuex
app.use(store)

// ps: 相关的install,一定要在mount之前注册

app.mount('#app')

写在最后

正文到这里差不多就结束了。说句题外话,vue3.x对于vue2.x来说更新的还是蛮多的,除了我们已经说烂了的响应式重写,静态属性提升优化,diff最长递增子序列等等,写法上也有了很大不同。我个人还是蛮喜欢函数式编程的写法,兄弟萌可以在平时无聊的时候玩一玩。

emmmmmm,虽然element-ui已经基本不更新了,更别说适配vue3。组件库说实话,确实是个问题,不过这个之后肯定也是会解决的。

这次分享到这里就结束了,觉得有用的兄弟萌可以点个赞。文中有不对的地方,也希望大佬们帮我指出改正,hhhh。
image

查看原文

赞 11 收藏 7 评论 2

Ekko_ 赞了文章 · 7月23日

使用js-xlsx纯前端导出excel

前言

最近公司需要将几张统计表格导出到excel,由于公司现有导出excel功能是前后端配合的导出,觉得麻烦,所以想找一个纯前端导出的工具,最后找到了js-xlsx,评价还是挺高的,但是中文文档没找到,百度也没有找到一个比较全面的教程,所以踩了很多坑,自己记录下,方便以后使用。

环境

由于我业务只用到将table标签内的内容导出到excel,所以只会写如何将一个table元素里的内容导出到excel。也可以通过json导出,貌似还会更简单些。

安装

GitHub地址
npm安装

npm install xlsx

安装后dist文件夹下有一个文件xlsx.full.min.js,就是它了,引入到项目中

第一个例子

先上代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <table id="table1" border="1" cellspacing="0" cellpadding="0" >
        <thead>
            <tr>
                <td>序号</td>
                <td>姓名</td>
                <td>年龄</td>
                <td>兴趣</td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>张三</td>
                <td>18</td>
                <td>打游戏</td>
            </tr>
            <tr>
                <td>2</td>
                <td>李四</td>
                <td>88</td>
                <td>看电影</td>
            </tr>
            <tr>
                <td>3</td>
                <td>王五</td>
                <td>81</td>
                <td>睡觉</td>
            </tr>
        </tbody>
    </table>

    <button id="btn" onclick="btn_export()">导出</button>
</body>
<script data-original="js/xlsx.full.min.js"></script>
<script data-original="js/export.js"></script>
<script>
    function btn_export() {
        var table1 = document.querySelector("#table1");
        var sheet = XLSX.utils.table_to_sheet(table1);//将一个table对象转换成一个sheet对象
        openDownloadDialog(sheet2blob(sheet),'下载.xlsx');
    }
</script>
</html>

运行效果
这是个运行效果

导出结果:

在这里插入图片描述
你可能注意到了,我这里引入了一个export.js文件,这个export.js文件里面只有2个方法,就是上面代码用到的openDownloadDialog(sheet2blob(sheet),'下载.xlsx');
这是export.js的代码:

// 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
function sheet2blob(sheet, sheetName) {
    sheetName = sheetName || 'sheet1';
    var workbook = {
        SheetNames: [sheetName],
        Sheets: {}
    };
    workbook.Sheets[sheetName] = sheet; // 生成excel的配置项

    var wopts = {
        bookType: 'xlsx', // 要生成的文件类型
        bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
        type: 'binary'
    };
    var wbout = XLSX.write(workbook, wopts);
    var blob = new Blob([s2ab(wbout)], {
        type: "application/octet-stream"
    }); // 字符串转ArrayBuffer
    function s2ab(s) {
        var buf = new ArrayBuffer(s.length);
        var view = new Uint8Array(buf);
        for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
        return buf;
    }
    return blob;
}

function openDownloadDialog(url, saveName) {
    if (typeof url == 'object' && url instanceof Blob) {
        url = URL.createObjectURL(url); // 创建blob地址
    }
    var aLink = document.createElement('a');
    aLink.href = url;
    aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
    var event;
    if (window.MouseEvent) event = new MouseEvent('click');
    else {
        event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    }
    aLink.dispatchEvent(event);
}

PS: 这2个方法是网上当的,原文地址。作者写的挺好,也是从这里找到了头绪。

如果你的table标签内有合并单元格的操作,XLSX.utils.table_to_sheet(*)也能够读取出来,并且你打印出来的结果也能够显示出来,效果图:
在这里插入图片描述
在这里插入图片描述
可以看到,excel中的表格也已经合并了。
但是实际的情况,客户觉得这行字没有居中,他就会向你唠叨,为啥不居中,所以我们现在解决文字不居中的问题。

设置样式(居中,文字大小颜色,背景色...)

PS:这是我踩坑最多的地方....

这里就不绕圈子了,设置样式的话,上面的xlsx.full.min.js是无法生效的,
必须安装xlsx-style

安装xlsx-style

好像只有npm安装,github我没找到地址

npm install xlsx-style

同样,安装目录下dist文件夹下有一个xlsx.full.min.js,嗯?名字一模一样?怎么用?好吧,无从下手,只好硬着头皮引入了,注意,我将xlsx-style的js文件放在下方:
在这里插入图片描述
还有btn_export()方法要变一下,加一下样式。
具体的单元格样式说明可以看下这篇文章 xlsx-style单元格样式参考表

function btn_export() {
        var table1 = document.querySelector("#table1");
        var sheet = XLSX.utils.table_to_sheet(table1);
        //这个就是修改格式的代码
        sheet["A5"].s = { 
            font: { sz: 13, bold: true, },
            alignment: { 
                horizontal: "center", vertical: "center", wrap_text: true 
            } 
        };
        openDownloadDialog(sheet2blob(sheet),'下载.xlsx');
    }

改完之后,点击运行,果不其然,报错了:
在这里插入图片描述

原因是什么呢,原因是2个js文件暴露出来的变量都叫‘XLSX’,但是xlsx-style这个js文件里没有XLSX.utils这个方法,而且xlsx-style这个js文件是后引入的,就把前面的XLSX给覆盖了,所以报错。
XLSX.utils里面有很多可用的方法,但是按照这种方式无法进行调用:
在这里插入图片描述
你可能想到把2个js文件调换一下位置,但是结果是xlsx暴露的变量覆盖了xlsx-style暴露的变量。你的样式还是改变不了。

注意

如果你的导出功能 是传入json格式或其他格式而没有用到XLSX.utils的话,你只需使用xlsx-style的js,下面的内容可以忽略,下面的内容是讲如何使xlsx和xlsx-style的js一起工作的。

不用XLSX.utils的方式

由于这2个js都是加密之后的内容,无法解读,不能在这2个js上找到什么有用的东西。好在在xlsx dist文件夹下找到了xlsx.extendscript.js,看这个文件就像个工具类,由于我上面用到了table_to_sheet方法,在xlsx.extendscript上面的搜索了一下,果然发现了这个方法,二话不说,将xlsx的js引用删除,引入xlsx.extendscript:
在这里插入图片描述

运行。结果你应该已经猜到了,样式并没有发生改变。什么原因呢,xlsx.extendscript.js暴露出来的变量仍然是'XLSX',下面的变量还是覆盖了上面的变量。

注意!!!

如果你的项目中使用了webpack、babel等,可以直接import,不用改变变量名

好在这个xlsx.extendscript.js不是压缩版本,可以对内容进行修改,就把暴露出来的变量修改为'XLSX2'吧。这样我们只有在使用utils工具的时候才用到xlsx.extendscript.js,其余都用的是xlsx-style这个js,这样总该可以了吧 。
修改完之后别忘了将XSLX.utils.table_to_sheet()改成XLSX2.utils.table_to_sheet()。
(不建议修改源码,由于工作需要不修改源码无法使用才做的修改)

function btn_export() {
        var table1 = document.querySelector("#table1");
        var sheet = XLSX2.utils.table_to_sheet(table1);
        sheet["A5"].s = {
            font: {
                sz: 13,
                bold: true,
                color: {
                    rgb: "FFFFAA00"
                }
            },
            alignment: {
                horizontal: "center",
                vertical: "center",
                wrap_text: true
            }
        };
        openDownloadDialog(sheet2blob(sheet), '下载.xlsx');
    }

运行:
在这里插入图片描述

可以看到,你所做的样式更改已经生效了。
客户需求增加:我想要前面几行空出来,并且写上打印公司名。
观察xlsx.extendscript.js源码,发现table_to_sheet,也就是parse_dom_table,并没有设置起始行的参数,下面给出parse_dom_table的代码:

function parse_dom_table(table, _opts) {
    var opts = _opts || {};
    if(DENSE != null) opts.dense = DENSE;
    var ws = opts.dense ? ([]) : ({});
    var rows = table.getElementsByTagName('tr');
    var sheetRows = opts.sheetRows || 10000000;
    var range = {s:{r:0,c:0},e:{r:0,c:0}};
    var merges = [], midx = 0;
    var rowinfo = [];
    var _R = 0, R = 0, _C, C, RS, CS;
    for(; _R < rows.length && R < sheetRows; ++_R) {
        var row = rows[_R];
        if (is_dom_element_hidden(row)) {
            if (opts.display) continue;
            rowinfo[R] = {hidden: true};
        }
        var elts = (row.children);
        for(_C = C = 0; _C < elts.length; ++_C) {
            var elt = elts[_C];
            if (opts.display && is_dom_element_hidden(elt)) continue;
            var v = htmldecode(elt.innerHTML);
            for(midx = 0; midx < merges.length; ++midx) {
                var m = merges[midx];
                if(m.s.c == C && m.s.r <= R && R <= m.e.r) { C = m.e.c+1; midx = -1; }
            }
            /* TODO: figure out how to extract nonstandard mso- style */
            CS = +elt.getAttribute("colspan") || 1;
            if((RS = +elt.getAttribute("rowspan"))>0 || CS>1) merges.push({s:{r:R,c:C},e:{r:R + (RS||1) - 1, c:C + CS - 1}});
            var o = {t:'s', v:v};
            var _t = elt.getAttribute("t") || "";
            if(v != null) {
                if(v.length == 0) o.t = _t || 'z';
                else if(opts.raw || v.trim().length == 0 || _t == "s"){}
                else if(v === 'TRUE') o = {t:'b', v:true};
                else if(v === 'FALSE') o = {t:'b', v:false};
                else if(!isNaN(fuzzynum(v))) o = {t:'n', v:fuzzynum(v)};
                else if(!isNaN(fuzzydate(v).getDate())) {
                    o = ({t:'d', v:parseDate(v)});
                    if(!opts.cellDates) o = ({t:'n', v:datenum(o.v)});
                    o.z = opts.dateNF || SSF._table[14];
                }
            }
            if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = o; }
            else ws[encode_cell({c:C, r:R})] = o;
            if(range.e.c < C) range.e.c = C;
            C += CS;
        }
        ++R;
    }
    if(merges.length) ws['!merges'] = merges;
    if(rowinfo.length) ws['!rows'] = rowinfo;
    range.e.r = R - 1;
    ws['!ref'] = encode_range(range);
    if(R >= sheetRows) ws['!fullref'] = encode_range((range.e.r = rows.length-_R+R-1,range)); // We can count the real number of rows to parse but we don't to improve the performance
    return ws;
}

那自己加一个吧
可以看到,里面的R变量 这是控制起始行的关键所在,好吧,我们再做一下修改:

var _R = 0, R = _opts.rowIndex || 0, _C, C, RS, CS;

这里我们给_opts增加一个属性rowIndex,在调用table_to_sheet方法的时候传入这个属性。下面是变更后的代码:

function btn_export() {
        var table1 = document.querySelector("#table1");
        var opt = {
            rowIndex: 4
        }; //开头空4行
        var sheet = XLSX2.utils.table_to_sheet(table1, opt);
        sheet["A1"] = {
            t: "s",
            v: '三鹿集团有限公司'
        }; //给A1单元格赋值
        sheet["A1"].s = {
            font: {
                name: '宋体',
                sz: 24,
                bold: true,
                underline: true,
                color: {
                    rgb: "FFFFAA00"
                }
            },
            alignment: { horizontal: "center", vertical: "center", wrap_text: true },
            fill: {
                bgColor: { rgb: 'ffff00' }
            }
        };
        //["!merges"]这个属性是专门用来进行单元格合并的 
        sheet["!merges"].push({//如果不为空push 为空 = 赋值
            //合并单元格 index都从0开始
            s: { //s开始
                c: 0, //开始列
                r: 0 //开始行
            },
            e: { //e结束
                c: 3, //结束列
                r: 2 //结束行
            }
        });
        sheet["A9"].s = { //样式
            font: {
                sz: 13,
                bold: true,
                color: {
                    rgb: "FFFFAA00"
                }
            },
            alignment: {
                horizontal: "center",
                vertical: "center",
                wrap_text: true
            }
        };
        openDownloadDialog(sheet2blob(sheet), '下载.xlsx');
    }

运行结果:
在这里插入图片描述
可以看到,你所做的更改生效了。

客户又提新需求了,要加上2个字段,身份证号和手机号。
这还不简单?加上2个字段不就好了。2分钟搞定,导出:
在这里插入图片描述
???
身份证号怎么变成了科学计数法,什么鬼(后来发现百分比也会直接给你换算成0~1的小数,统计没法搞)
怎么回事?还是parse_dom_table的杰作!
注意这一行:

else if(!isNaN(fuzzynum(v))) o = {t:'n', v:fuzzynum(v)};

意思是只要从td的text里读取到的值,只要转换之后是一个number,(不管你是string类型),都会给你来一个fuzzynum(v),转换成一个number类型。
做下修改,结果:

function parse_dom_table(table, _opts) {
    var opts = _opts || {};
    if(DENSE != null) opts.dense = DENSE;
    var ws = opts.dense ? ([]) : ({});
    var rows = table.getElementsByTagName('tr');
    var sheetRows = opts.sheetRows || 10000000;
    var range = {s:{r:0,c:0},e:{r:0,c:0}};
    var merges = [], midx = 0;
    var rowinfo = [];
    var _R = 0, R = _opts.rowIndex || 0, _C, C, RS, CS;
    for(; _R < rows.length && R < sheetRows; ++_R) {
        var row = rows[_R];
        if (is_dom_element_hidden(row)) {
            if (opts.display) continue;
            rowinfo[R] = {hidden: true};
        }
        var elts = (row.children);
        for(_C = C = 0; _C < elts.length; ++_C) {
            var elt = elts[_C];
            if (opts.display && is_dom_element_hidden(elt)) continue;
            var v = htmldecode(elt.innerHTML);
            for(midx = 0; midx < merges.length; ++midx) {
                var m = merges[midx];
                if(m.s.c == C && m.s.r <= R && R <= m.e.r) { C = m.e.c+1; midx = -1; }
            }
            /* TODO: figure out how to extract nonstandard mso- style */
            CS = +elt.getAttribute("colspan") || 1;
            if((RS = +elt.getAttribute("rowspan"))>0 || CS>1) merges.push({s:{r:R,c:C},e:{r:R + (RS||1) - 1, c:C + CS - 1}});
            var o = {t:'s', v:v};
            var _t = elt.getAttribute("t") || "";
            if(v != null) {
                if(v.length == 0) o.t = _t || 'z';
                else if(opts.raw || v.trim().length == 0 || _t == "s"){}
                else if(v === 'TRUE') o = {t:'b', v:true};
                else if(v === 'FALSE') o = {t:'b', v:false};
        //else if(!isNaN(fuzzynum(v))) o = {t:'n', v:fuzzynum(v)};
        else if(!isNaN(fuzzynum(v))) o = {t:'s', v:v};//不自动格式化number类型
                else if(!isNaN(fuzzydate(v).getDate())) {
                    o = ({t:'d', v:parseDate(v)});
                    if(!opts.cellDates) o = ({t:'n', v:datenum(o.v)});
                    o.z = opts.dateNF || SSF._table[14];
                }
            }
            if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = o; }
            else ws[encode_cell({c:C, r:R})] = o;
            if(range.e.c < C) range.e.c = C;
            C += CS;
        }
        ++R;
    }
    if(merges.length) ws['!merges'] = merges;
    if(rowinfo.length) ws['!rows'] = rowinfo;
    range.e.r = R - 1;
    ws['!ref'] = encode_range(range);
    if(R >= sheetRows) ws['!fullref'] = encode_range((range.e.r = rows.length-_R+R-1,range)); // We can count the real number of rows to parse but we don't to improve the performance
    return ws;
}

将转换的语句注释掉,重写这行代码,如果是number类型,不做任何修改,该是什么值还是什么值。
现在再重新运行,结果:
在这里插入图片描述可以看到,数字能够正常显示了。但是这个单元格好像并不会自动展开,永远都这么大,xlsx-style 也提供了控制单元格宽度的方法:

sheet["!cols"] = [{
            wpx: 70
        }, {
            wpx: 70
        }, {
            wpx: 70
        }, {
            wpx: 70
        }, {
            wpx: 150
        }, {
            wpx: 120
        }]; //单元格列宽

注意,设置单元格列宽要从第一行开始设置
结果:
在这里插入图片描述
完整前端代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <table id="table1" border="1" cellspacing="0" cellpadding="0">
        <thead>
            <tr>
                <td>序号</td>
                <td>姓名</td>
                <td>年龄</td>
                <td>兴趣</td>
                <td>身份证号</td>
                <td>手机号</td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>张三</td>
                <td>18</td>
                <td>打游戏</td>
                <td>320322184087562589</td>
                <td>1374569821</td>
            </tr>
            <tr>
                <td>2</td>
                <td>李四</td>
                <td>88</td>
                <td>看电影</td>
                <td>420322184087562589</td>
                <td>2374569821</td>
            </tr>
            <tr>
                <td>3</td>
                <td>王五</td>
                <td>81</td>
                <td>睡觉</td>
                <td>520322184087562589</td>
                <td>3374569821</td>
            </tr>
            <tr>
                <td colspan="4">这是一个合并单元格</td>
            </tr>
        </tbody>
    </table>

    <button id="btn" onclick="btn_export()">导出</button>
</body>
<script data-original="js/xlsx.extendscript.js"></script>
<script data-original="js/xlsx-style/xlsx.full.min.js"></script>

<script data-original="js/export.js"></script>
<script>
    function btn_export() {
        var table1 = document.querySelector("#table1");
        var opt = {
            rowIndex: 4
        }; //开头空4行
        var sheet = XLSX2.utils.table_to_sheet(table1, opt);
        sheet["A1"] = {
            t: "s",
            v: '三鹿集团有限公司'
        }; //给A1单元格赋值
        sheet["A1"].s = {
            font: {
                name: '宋体',
                sz: 24,
                bold: true,
                underline: true,
                color: {
                    rgb: "FFFFAA00"
                }
            },
            alignment: {
                horizontal: "center",
                vertical: "center",
                wrap_text: true
            },
            fill: {
                bgColor: {
                    rgb: 'ffff00'
                }
            }
        };
        //["!merges"]这个属性是专门用来进行单元格合并的 
        sheet["!merges"].push({ //如果不为空push 为空 = 赋值
            //合并单元格 index都从0开始
            s: { //s开始
                c: 0, //开始列
                r: 0 //开始行
            },
            e: { //e结束
                c: 3, //结束列
                r: 2 //结束行
            }
        });
        sheet["A9"].s = { //样式
            font: {
                sz: 13,
                bold: true,
                color: {
                    rgb: "FFFFAA00"
                }
            },
            alignment: {
                horizontal: "center",
                vertical: "center",
                wrap_text: true
            }
        };

        sheet["!cols"] = [{
            wpx: 70
        }, {
            wpx: 70
        }, {
            wpx: 70
        }, {
            wpx: 70
        }, {
            wpx: 150
        }, {
            wpx: 120
        }]; //单元格列宽

        openDownloadDialog(sheet2blob(sheet), '下载.xlsx');
    }
</script>

</html>

demo源码

github地址 完整实例

总结

  1. 不是特殊情况不建议修改源码
  2. 因为毕竟修改了代码,所以这种方法只能面向小众
  3. 听过收费版功能很全,建议如果有需要的话还是购买收费版本,但是地址没找到...
  4. 可以根据自己需求对xlsx源码进行修改,以便满足自己工作的需求。但是这样较难以维护,如何取舍还是自行斟酌。
  5. 我这里只列取了我实际工作中所需要的功能,xlsx的功能很丰富,有空可以多琢磨琢磨。
查看原文

赞 10 收藏 6 评论 5

Ekko_ 发布了文章 · 6月1日

深入理解Vue的插件机制与install

前言

我们在使用Vue的时候,经常会使用并写一些自定义的插件,然后利用Vue.use引入。所以提到写插件,install这个方法是必不可少的。Vue.js 的插件应该暴露一个 `install` 方法。这个方法的第一个参数是 `Vue` 构造器,第二个参数是一个可选的选项对象。这是Vue官方对Vue插件的规范。那这install函数到底是什么东东呢,Vue内部到底用它做了什么处理,怎么调用的,今天我就给大家伙从源码层面把他整的明明白白。
看完这篇文章,你将学到:

  • install函数可以做些什么;
  • install内部是怎么实现的;
  • Vuex,Vue-Router插件在install期间到底干了什么;

好啦,闲话不多说,咱们直接开始!!!
d4c88a54f4f720394d3ac795268dd6c6.jpg

install在Vuex&Vue-Router中的处理

这里先抛出两个问题,大家可以思考下,算是挖坑,下面再逐一解答:

  • 为什么我们在项目中可以直接使用$router $store来获取其中的值以及一些方法;
  • 为什么使用这俩插件都是先用Vue.use引入。然后才创建实例,在Vue实例中传入;

二者其实原理相同,这里我们用Vue-Router来举例,首先我们来看一下它内部install的具体实现:

class Router {
    constructor(options) {
        ...
    }
}

Router.install = function(_Vue) {

    _Vue.mixin({
        beforeCreate() {
            if (this.$options.router) {
                _Vue.prototype.$router = this.$options.router
            }
        }
    })

}

export default Router;
  • _Vue.mixin全局混入是什么呢?相当于在所有的组件中混入这个方法;
  • beforeCreate是什么呢?当然是Vue的一个生命周期,在create之前执行;

既然如此,我们大胆的做一个判断。Vue-Router其实是在install函数里面使用了一个全局混入,在beforeCreate这个生命周期触发的时候把this.$options.router挂载到Vue的原型上,这样我们就可以使用this.$router来调用router实例啦。
同学A:等一下,stop!!!你说的我很李姐,但是this.$options.router这又是什么东西,从哪来的啊?
141239d5226d826b85e775d02d9d359c.jpg

安啦,这咱们才刚刚解决了第一个问题,下面咱们来填第二个坑。

咱们平时使用Vue-Router,以及定义入口文件的Vue实例大概是这样子

// router/index.js
import VueRouter form 'vue-router';
import Vue from 'vue';

Vue.use(VueRouter);

const _router = [
    ...
]

const Router = new VueRouter(_router);

export default Router;

// main.js
import Vue from 'vue';
import router from 'router';

new Vue({
    router,
    ...
}).$mount('#app');

结合最开始的例子,我们先来分析一波。

  • Vue.use()主要是调用插件内部的install方法,并将Vue实例作为参数传入;
  • 上面使用的是this.$options.router,options通常代表的是配置项;
  • 在main.js中我们把Router实例作为配置项传入到Vue实例中

叮!!!要素察觉,那我们来大胆推测一波。
Vue-Routeruse其实是做了一个全局混入,为了在合适的时间点,获取到Vue根实例配置项中的router实例,执行挂载。紧接着在new Vue()根实例创建的时候,注入router实例,然后触发全局混入中的生命周期,这个时候根实例的配置项this.$options已经包含了router实例,最后完成挂载流程!!!
光这一段的代码也是逻辑缜密,编程思路巧妙,令人直呼内行啊!兄弟萌,把内行打在公屏上,hhhh。
c16b9c11f6d1eb1cf4e03ab9651e1c83.jpg

install在Vue中的内部实现

看完了常用库install的使用,不知大家是否有收获。接下来热身结束后,我们就可以开始看一看install内部实现了,先上源码。

export function initUse (Vue: GlobalAPI) {
    // 注册一个挂载在实例上的use方法
    Vue.use = function (plugin: Function | Object) {
        // 初始化当前插件的数组
        const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
        // 如果这个插件已经被注册过了,那就不作处理
        if (installedPlugins.indexOf(plugin) > -1) {

            return this

        }

        ...
        
        // 重点来了哦!!!
        if (typeof plugin.install === 'function') {
        // 当插件中install是一个函数的时候,调用install方法,指向插件,并把一众参数传入
            plugin.install.apply(plugin, args)

        } else if (typeof plugin === 'function') {
        // 当插件本身就是一个函数的时候,把它当做install方法,指向插件,并把一众参数传入
            plugin.apply(null, args)

        }
        
        // 将插件放入插件数组中
        installedPlugins.push(plugin)

        return this
    }
}

源码这部分写的很简洁,可读性很高。就是在use的时候,判断插件类型,执行install或者插件本身。其实细化一下官网的解释就是,Class类的插件应该暴露一个 install 方法。

结语

这次的分享到这里就结束啦,不知道大家对于Vue的插件机制有没有更深入的了解呢?其实开发插件的时候利用install我们可以做茫茫多的事儿。
比如Vue-Router在install中其实还注册了Router-viewRouter-link的全局组件。感兴趣的同学们可以去看一下vue-router的原理?我们来手撸一个vue-router!
再次感谢你的阅读。好啦,兄弟萌再见咯!
6ef565bd9a73e980f903d7c44d329acc.jpg

查看原文

赞 0 收藏 0 评论 0

Ekko_ 赞了文章 · 5月12日

Node.js使用Nodemailer发送邮件

原文链接:Node.js使用Nodemailer发送邮件

电子邮件是—种用电子手段提供信息交换的通信方式,是互联网应用最广的服务。通过网络的电子邮件系统,用户可以以非常低廉的价格(不管发送到哪里,都只需负担网费)、非常快速的方式(几秒钟之内可以发送到世界上任何指定的目的地),与世界上任何一个角落的网络用户联系。

在很多项目中,我们都会遇到邮件注册,邮件反馈等需求。在node中收发电子邮件也非常简单,因为强大的社区有各种各样的包可以供我么直接使用。Nodemailer包就可以帮助我们快速实现发送邮件的功能。

Github源码:https://github.com/ogilhinn/node-abc/tree/master/lesson10

Nodemailer简介

Nodemailer是一个简单易用的Node.js邮件发送组件

官网地址:https://nodemailer.com

GitHub地址:https://github.com/nodemailer/nodemailer

Nodemailer的主要特点包括:

  • 支持Unicode编码
  • 支持Window系统环境
  • 支持HTML内容和普通文本内容
  • 支持附件(传送大附件)
  • 支持HTML内容中嵌入图片
  • 支持SSL/STARTTLS安全的邮件发送
  • 支持内置的transport方法和其他插件实现的transport方法
  • 支持自定义插件处理消息
  • 支持XOAUTH2登录验证

安装使用

首先,我们肯定是要下载安装 注意:Node.js v6+

npm install nodemailer --save

打开官网可以看见一个小例子

'use strict';
const nodemailer = require('nodemailer');

// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
nodemailer.createTestAccount((err, account) => {

    // create reusable transporter object using the default SMTP transport
    let transporter = nodemailer.createTransport({
        host: 'smtp.ethereal.email',
        port: 587,
        secure: false, // true for 465, false for other ports
        auth: {
            user: account.user, // generated ethereal user
            pass: account.pass  // generated ethereal password
        }
    });

    // setup email data with unicode symbols
    let mailOptions = {
        from: '"Fred Foo ?" <foo@blurdybloop.com>', // sender address
        to: 'bar@blurdybloop.com, baz@blurdybloop.com', // list of receivers
        subject: 'Hello ✔', // Subject line
        text: 'Hello world?', // plain text body
        html: '<b>Hello world?</b>' // html body
    };

    // send mail with defined transport object
    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            return console.log(error);
        }
        console.log('Message sent: %s', info.messageId);
        // Preview only available when sending through an Ethereal account
        console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));

        // Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@blurdybloop.com>
        // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
    });
});

这个小例子是生成了Ethereal的账户进行邮件发送演示的。但是这多没意思,我们来使用自己的邮箱来发送邮件

发出个真实的邮件

这里我使用了我的qq邮箱给163邮箱发送email。

'use strict';

const nodemailer = require('nodemailer');

let transporter = nodemailer.createTransport({
  // host: 'smtp.ethereal.email',
  service: 'qq', // 使用了内置传输发送邮件 查看支持列表:https://nodemailer.com/smtp/well-known/
  port: 465, // SMTP 端口
  secureConnection: true, // 使用了 SSL
  auth: {
    user: 'xxxxxx@qq.com',
    // 这里密码不是qq密码,是你设置的smtp授权码
    pass: 'xxxxxx',
  }
});

let mailOptions = {
  from: '"JavaScript之禅" <xxxxx@qq.com>', // sender address
  to: 'xxxxxxxx@163.com', // list of receivers
  subject: 'Hello', // Subject line
  // 发送text或者html格式
  // text: 'Hello world?', // plain text body
  html: '<b>Hello world?</b>' // html body
};

// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('Message sent: %s', info.messageId);
  // Message sent: <04ec7731-cc68-1ef6-303c-61b0f796b78f@qq.com>
});

运行程序,成功将返回messageId。这是便可以去收件箱查看这个新邮件啦

email

这里我们需要注意,auth.pass 不是邮箱账户的密码而是stmp的授权码。

到此我们就掌握了发邮件的基本操作。

更多配置

  • CC: Carbon Copy(抄送),用于通知相关的人,收件人可以看到都邮件都抄送给谁了。一般回报工作或跨部门沟通时,都会CC给收件人的领导一份
  • BCC:Blind Carbon Copy(暗抄送),也是用于通知相关的人,但是收件人是看不到邮件被密送给谁了。
  • attachments: 附件

更多配置项:https://nodemailer.com/message/

这里我们就不演示CC、BCC了,请自行尝试。我们来试试发送附件

...
// 只需添加attachments配置项即可
attachments: [
    {   // utf-8 string as an attachment
      filename: 'text.txt',
      content: 'hello world!'
    },
    {
      filename: 'ZenQcode.png',
      path: path.resolve(__dirname, 'ZenQcode.png'),
    }
  ]
...

发送email,就可以收到一个内容为hello world的text.txt文件,以及一个我公众号的二维码。

好看的HTML邮件

HTML Email 编写指南: http://www.ruanyifeng.com/blog/2013/06/html_email.html

这儿,我们使用Foundation for Emails: https://foundation.zurb.com/emails.html的模板

'use strict';

const nodemailer = require('nodemailer');
const ejs = require('ejs');
const fs  = require('fs');
const path = require('path');

let transporter = nodemailer.createTransport({
  // host: 'smtp.ethereal.email',
  service: 'qq', // 使用内置传输发送邮件 查看支持列表:https://nodemailer.com/smtp/well-known/
  port: 465, // SMTP 端口
  secureConnection: true, // 使用 SSL
  auth: {
    user: 'xxxxxx@qq.com',
    // 这里密码不是qq密码,是你设置的smtp授权码
    pass: 'xxxxxx',
  }
});

let mailOptions = {
  from: '"JavaScript之禅" <xxxxx@qq.com>', // sender address
  to: 'xxxxxxxx@163.com', // list of receivers
  subject: 'Hello', // Subject line
  // 发送text或者html格式
  // text: 'Hello world?', // plain text body
  html: fs.createReadStream(path.resolve(__dirname, 'email.html')) // 流
};

// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
  if (error) {
    return console.log(error);
  }
  console.log('Message sent: %s', info.messageId);
  // Message sent: <04ec7731-cc68-1ef6-303c-61b0f796b78f@qq.com>
});

运行程序,你将如愿以偿收到如下Email。样式可能会有细微偏差

屏幕快照 2017-12-01 16.32.41

上面email中我们用了外链的图片,我们也可以使用附件的方式,将图片嵌入进去。给附件加个cid属性即可。

...
let mailOptions = {
  ...
  html: '<img data-original="cid:01">', // html body
  attachments: [
    {
      filename: 'ZenQcode.png',
      path: path.resolve(__dirname, 'ZenQcode.png'),
      cid: '01',
    }
  ]
};
...

使用模板引擎

邮件信息一般都不是固定的,我们可以引入模板引擎对HTML内容进行渲染。

这里我们使用Ejs:https://github.com/mde/ejs/来做演示

$ npm install ejs --save

ejs具体语法请参看官方文档

先建立一个email.ejs文件

<h1>hello <%= title %></h1>
<p><%= desc %></p>

修改我们的js文件

...
const template = ejs.compile(fs.readFileSync(path.resolve(__dirname, 'email.ejs'), 'utf8'));

const html = template({
  title: 'Ejs',
  desc: '使用Ejs渲染模板',
});

let mailOptions = {
  from: '"JavaScript之禅" <xxxxx@qq.com>', // sender address
  to: 'xxxxx@163.com', // list of receivers
  subject: 'Hello', // Subject line
  html: html,// html body
};
...

到此,你的邮箱将收到一个动态渲染的hello Ejs。

本文到此告一段落,在此基础上你可以实现更多有用的功能

HTML email 框架推荐

左手代码,右手砖,抛砖引玉

如果你知道更多好用HTML email资源,留言交流让更多人知道。

最后福利干货来了

36个国内外精致电子邮件HTML模版

产品周报类预览

投票

知乎周报

关注公众号【JavaScript之禅】回复【 666 】,免费获取

JavaScript之禅

查看原文

赞 48 收藏 53 评论 2

Ekko_ 赞了回答 · 4月28日

解决 如何遍历vue上data的所有属性

let params = {
                     sbjx:'1',sbxh:'2',gl:'',glys:'',
                     dy:'',dl:'',zs:'',zl:'',fhdj:'',jydj:'3',
                     pl:'',gys:'',azdd:'AA',gzz:'',lqsj:'4',
                     zzrq:'',pp:'',dzdy:'',xl:'',hjwd:'5',
                     yy:'',nxdj:'',zcxh:'',jzzq:'',edgl:'',
                     eddy:'',eddl:'',edzs:'',bz:'',id:'11',name:'3'
}

data () {
    return {
        params
    }
}

然后遍历this.params

或者, 遍历this._data

关注 4 回答 3

Ekko_ 发布了文章 · 4月28日

flutter中如何监听键盘弹出关闭

前言

最近在做公司flutter项目的时候,接到了一个需求,本质上就是实现收起键盘的时候让TextField组件失去焦点的功能。

这个需求乍一看很好解决,心想,就这!就这!就这!so easy!

但是!但是!但是!万万没想到啊,实现时候却让我本来就很稀薄的头发雪上加霜,原因就是安卓手机第三方输入法有一个很蛋疼的地方,就是他会有一个收起键盘的按钮,如下图所示:
image
问题就在,这!个!按!钮!他不属于原生键盘按钮,无法检测到键盘事件!并且不能够屏蔽!👴真的要给搞崩溃了。下面就给兄弟萌说说我最后的解题思路,希望能有所帮助!

解题思路

上面说过,键盘事件无法监听,所以我只能另外想办法。能不能监听键盘关闭呢?通过一番查找,得出的答案是可以,那么就来搞一下!

keyboard_visibility

地址: https://pub.dev/packages/keyb...

这个库可以用来监听键盘的弹出和收起,引用库里的例子:

import 'package:keyboard_visibility/keyboard_visibility.dart';

@protected
void initState() {
  super.initState();

  KeyboardVisibilityNotification().addNewListener(
    onChange: (bool visible) {
      print(visible);
    },
  );
}

通常来讲,这个就完全可以解决我们的需求,但是!事情并没有这么简单!我们在升级了flutter版本之后,发现无法打包apk,最后提示是keyboard_visibility不!兼!容!查了一下资料发现,这个库应该是作者停更了,没有做到与时俱进,这咋整!

image

由于网上相关资料极少,科学上网也不能立刻解决我的问题,万般无奈之下,只能换个思路。那能不能监听界面的高度变化?经过查找资料,得出的结论是可以这样做的。

WidgetsBindingObserver & didChangeMetrics

这个组件可以监听页面的一些生命周期,并且其中有一个回调didChangeMetrics可以监听界面高度的变化。其中键盘的弹出和收起这些其实都属于高度的变化自然也是可以监听到的。

Nice!看到这里,那么我们的解决方案也就有了,那就是设置一个flag,区分出来什么时候键盘弹出,什么时候键盘收起。

class _InputState extends State<Input> with WidgetsBindingObserver {
  // 输入框的焦点实例
  FocusNode _focusNode;
  // 当前键盘是否是激活状态
  bool isKeyboardActived = false;

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode();
   // 监听输入框焦点变化
    _focusNode.addListener(_onFocus);
    // 创建一个界面变化的观察者
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // 当前是安卓系统并且在焦点聚焦的情况下
      if (Platform.isAndroid && _focusNode.hasFocus) {
        if (isKeyboardActived) {
          isKeyboardActived = false;
          // 使输入框失去焦点
          _focusNode.unfocus();
          return;
        }
        isKeyboardActived = true;
      }
    });
  }

  // 既然有监听当然也要有卸载,防止内存泄漏嘛
  @override
  void dispose() {
    super.dispose();
    _focusNode.dispose();
    WidgetsBinding.instance.removeObserver(this);
  }

  // 焦点变化时触发的函数
  _onFocus() {
    if (_focusNode.hasFocus) {
      // 聚焦时候的操作
      return;
    }
    
    // 失去焦点时候的操作
    isKeyboardActived = false;
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      focusNode: _focusNode,
      textInputAction: TextInputAction.done,
    );
  }
}

这样就大功告成了,测试之后暂时没发现什么大问题,还是比较好用的。

参考文章

WidgetsBindingObserver监测页面生命周期

Flutter的生命周期(交互)

结语

分享大概也就到这里了,这个问题前前后后花了好长时间解决,我真的太难了,当然可能也是因为我九折水瓶的缘故吧...这次分享出来,大家有需要的话也能做个参考。如果有什么更好的解决方法的话大家伙儿也教教我,笔芯笔芯。

说句题外话,在flutter开发的过程中,坑还是很多的,和同事聊天不止一次吐槽flutter的周边生态,真的是稀碎,对比隔壁的RN一言难尽。并且通过这个事情让我认识到,跨平台这东西用原生组件好解决的话,尽量不使用相关库,不然哪天再整几个兼容问题,不得把👴整成一个卤蛋。

查看原文

赞 10 收藏 5 评论 3

Ekko_ 关注了标签 · 4月28日

react.js

React (sometimes styled React.js or ReactJS) is an open-source JavaScript library for creating user interfaces that aims to address challenges encountered in developing single-page applications. It is maintained by Facebook, Instagram and a community of individual developers and corporations.

关注 36888

Ekko_ 关注了标签 · 4月28日

css3

层叠样式表(英语:Cascading Style Sheets,简写CSS),又称串样式列表,由W3C定义和维护的标准,一种用来为结构化文档(如HTML文档或XML应用)添加样式(字体、间距和颜色等)的计算机语言。目前最新版本是CSS2.1,为W3C的候选推荐标准。CSS3现在已被大部分现代浏览器支持,而下一版的CSS4仍在开发过程中。

关注 23269

Ekko_ 关注了标签 · 4月28日

webpack

Webpack 是一个前端资源加载/打包工具,只需要相对简单的配置就可以提供前端工程化需要的各种功能。

关注 3977

Ekko_ 发布了文章 · 4月10日

30行代码使用node搞一个简单爬虫

前言

随着前端在工作中承担的职责不断扩大,node也成了前端进阶的必备技能,那么今天我们就来搞搞node。看完这篇文章,你将学到:

   - node一些插件库的认识和使用
   - 如何用node做一个简单的爬虫

好了,话不多说,接下来我们就用node搞点好玩的,兄弟萌,干就完了。

目标

做事情之前一定要目标明确,先规划一下我们要干点什么。

最近想下载点电影看看,去电影天堂去看的话,一页一页翻很是麻烦。并且众所周知程序员是一个很懒的群体,那么我们能不能通过写几行代码,把电影名称都爬下来,有想看的的直接去网站再搜索下载,答案当然是可以的。

既然目标已经确定了,下面就开始了。

代码实现

首先我们进入首页随便点进一部电影的详情,发现他的地址是https://www.dy2018.com/i/1018...
image.png

emmmmmm,好像明白了什么,再点进其他电影的详情地址是https://www.dy2018.com/i/1018...
image.png

那我们现在找到了它的规律,是依靠xxxxx.html来对电影进行编号访问的,那么我们接下来要做的就很简单了,只需要设定一个编号的范围,循环访问这些地址,在输出我们想要的东西就ok了。那么,好戏开场了,给👴爪巴。

    // 这里请求使用request库
    const originRequest = require('request');
    // 为服务器特别定制的,快速、灵活、实施的jQuery核心实现
    const cheerio = require('cheerio');
    // iconv-lite用于在node当中处理在各种操作系统出现的各种奇特编码,该模块不提供读写文件的操作,只提供文件编码转换的功能
    const iconv = require('iconv-lite');

    // 接下来我们封装一下request方法,减少代码重复,方便之后调用
    function request(url, cb) {
        let options = {
            encoding: null
        }

        originRequest(url, options, cb);
    }

    // 循环请求,获取到相应数据后进行编译拼装 这里我们爬取编号为100533--100563的30部电影名称
    for(let i = 100533; i < 100563; i++) {
        // 定义接口地址
        let url = `https://www.dy2018.com/i/${i}.html`;

        // 进行请求
        request(url, (error, request, body) => {
            // 将返回的内容进行编码
            const html = iconv.decode(body, 'gb2312');
            const $ = cheerio.load(html);

            // 读出我们想要的节点内容
            console.log(`名称:${$(".title_all h1").text()} --- 评分:${$('.rank').text() || 0} 分`);
        })
    }

好了,大功告成 接下来我们运行一下该文件, 输出一下结果:
image.png

结语

文章到这里就结束了,是不是很简单呢,觉得有兴趣的小伙伴自己下去玩一下。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 28 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-11-08
个人主页被 510 人浏览