-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
159 lines (76 loc) · 132 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>使用函数优雅地创建弹框抽屉</title>
<link href="/start/vue-create-dm/"/>
<url>/start/vue-create-dm/</url>
<content type="html"><![CDATA[<p>使用函数优雅地创建 <a href="https://www.antdv.com/docs/vue/introduce-cn/" title="ant-design-vue" target="_blank" rel="noopener">ant-design-vue</a>、<a href="https://www.iviewui.com/" title="view-design" target="_blank" rel="noopener">view-design</a>、<a href="https://element.eleme.cn/#/zh-CN" title="ElementUI" target="_blank" rel="noopener">ElementUI</a> 的 Drawer 和 Modal</p><h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><p><a href="https://hzyhbk.github.io/vue-create-dm/example/#/antd" title="普通项目中使用vue-create-dm例子在线地址">vue-create-dm使用例子在线地址</a></p><p>代码见 <code>example</code> 文件夹<br><a id="more"></a></p><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul><li>通过函数来创建<code>Modal</code>或<code>Drawer</code>组件</li><li><code>Modal</code>、<code>Drawer</code>的内容子组件的<code>created</code>、<code>mounted</code>、<code>destoryed</code>生命周期按照正常逻辑触发</li><li><code>Modal</code>、<code>Drawer</code>支持分别注册全局头部组件(需要接收名为 <code>title</code> 的 <code>props</code>)</li><li>支持传入 <code>title</code>、<code>content</code>、<code>footer</code> 插槽</li><li>支持<code>Modal</code>、<code>Drawer</code>与父组件通信</li><li>支持子组件获取 <code>this.$store</code> 和 <code>this.$router</code></li><li>支持传入路由来匹配<strong>内容</strong>组件,<ul><li>若传入<code>url</code>比如 <a href="https://www.baidu.com" target="_blank" rel="noopener">https://www.baidu.com</a> ,则以 <code>iframe</code>形式展示</li><li>若传入 <code>相对路由</code>(比如 /foo, /bar),则获取匹配的路由组件展示</li></ul></li><li>支持<strong>微前端中跨项目相互调用</strong></li></ul><h2 id="为什么"><a href="#为什么" class="headerlink" title="为什么"></a>为什么</h2><p>在使用弹窗抽屉组件的过程中,你是否也曾遇到过以下场景:</p><ol><li><p>一个项目里有许多的弹窗和抽屉类型的交互,有时甚至一个页面组件里就有许多弹窗和抽屉组件,原生的使用方式是先在父组件中写好弹框抽屉组件,然后通过<code>visible</code>变量来控制弹窗的显示隐藏,当弹窗抽屉一多,看着各种<code>xxVisible</code>让人感觉很混乱</p></li><li><p>弹窗抽屉内包含的子组件的生命周期并没有按我们预想的逻辑触发,我们想打开弹窗抽屉的时候才触发内容子组件的<code>created</code>和<code>mounted</code>生命周期,然而实际上却并不是;我们希望关闭的时候可以调用子组件的<code>destoryed</code>生命周期,可是目前的UI框架大多只是把组件设置为<code>display:none</code>了,并没有完全卸载子组件,</p></li><li><p><code>antd</code>提供了<code>destroyOnClose</code>参数支持关闭时销毁子元素,但也没法解决上面说到的1,2两点问题组件库虽然也有提供通过函数打开弹窗的方法,但那些都是一些简单的弹框,可配置的参数不多,自由度也不够高</p></li></ol><p>因此就有了<code>vue-create-dm</code>这个库,<code>dm</code>就是分别取了<code>Drawer</code>和<code>Modal</code>的第一个字母组合在一起(为什么不是<code>md</code>呢,因为<code>md</code>是<code>markdown</code>的缩写…)目前内置支持了<code>ant-design-vue</code>、<code>view-design</code>和<code>ElementUI</code>三个组件库的的弹框抽屉组件,并且提供了各种工具函数可以自己支持其他组件库的弹框抽屉组件</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add vue-create-dm</span><br></pre></td></tr></table></figure><h3 id="统一注册"><a href="#统一注册" class="headerlink" title="统一注册"></a>统一注册</h3><p><strong>注意1</strong><br>如果要在子组件内获取 <code>this.$store</code> 和 <code>this.$router</code> 请把 <code>VueCreateDM</code> 的注册放到 <code>Vuex</code> 和 <code>VueRouter</code> 实例生成之后,并且传入这两个实例</p><p><strong>注意2</strong><br>如果要自定义全局头部组件,请传入<code>modalGlobalHeader</code>,<code>drawerGlobalHeader</code>这两个参数,分别对应<code>Modal</code>组件的全局头部和<code>Drawer</code>组件的全局头部</p><p>下面演示如何进行全量注册:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span>;</span><br><span class="line"><span class="keyword">import</span> VueCreateDM <span class="keyword">from</span> <span class="string">'vue-create-dm'</span>;</span><br><span class="line"><span class="keyword">import</span> { Modal <span class="keyword">as</span> antdModal, Drawer <span class="keyword">as</span> antdDrawer } <span class="keyword">from</span> <span class="string">'ant-design-vue'</span>;</span><br><span class="line"><span class="keyword">import</span> { Modal <span class="keyword">as</span> viewModal, Drawer <span class="keyword">as</span> viewDrawer } <span class="keyword">from</span> <span class="string">'view-design'</span>;</span><br><span class="line"><span class="keyword">import</span> { Dialog <span class="keyword">as</span> eleModal, Drawer <span class="keyword">as</span> eleDrawer } <span class="keyword">from</span> <span class="string">'element-ui'</span>;</span><br><span class="line"><span class="keyword">import</span> store <span class="keyword">from</span> <span class="string">'./store'</span></span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">'./router'</span>;</span><br><span class="line"><span class="keyword">import</span> modalGlobalHeader <span class="keyword">from</span> <span class="string">'./components/modalGlobalHeader'</span>;</span><br><span class="line"><span class="keyword">import</span> drawerGlobalHeader <span class="keyword">from</span> <span class="string">'./components/drawerGlobalHeader'</span>;</span><br><span class="line"></span><br><span class="line">Vue.use(VueCreateDM, {</span><br><span class="line"> antdModal,</span><br><span class="line"> antdDrawer,</span><br><span class="line"> viewModal,</span><br><span class="line"> viewDrawer,</span><br><span class="line"> eleModal,</span><br><span class="line"> eleDrawer,</span><br><span class="line"> store,</span><br><span class="line"> router,</span><br><span class="line"> modalGlobalHeader,</span><br><span class="line"> drawerGlobalHeader,</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><h3 id="单个注册"><a href="#单个注册" class="headerlink" title="单个注册"></a>单个注册</h3><p><strong>注意1</strong><br>如果要在子组件内获取 <code>this.$store</code> 和 <code>this.$router</code> 请把 <code>VueCreateDM</code> 的注册放到 <code>Vuex</code> 和 <code>VueRouter</code> 实例生成之后,并且传入这两个实例</p><p><strong>注意2</strong><br>如果要自定义全局头部组件,请传入<code>globalHeader</code>参数</p><p>下面演示如何单个注册,<strong>其中<code>component</code>属性必传</strong>,其余几个都是可选参数:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span>;</span><br><span class="line"><span class="keyword">import</span> store <span class="keyword">from</span> <span class="string">'./store'</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">'./router'</span>;</span><br><span class="line"><span class="keyword">import</span> { createAntdDrawer } <span class="keyword">from</span> <span class="string">'vue-create-dm'</span>;</span><br><span class="line"><span class="keyword">import</span> { Drawer } <span class="keyword">from</span> <span class="string">'ant-design-vue'</span>;</span><br><span class="line"><span class="keyword">import</span> globalHeader <span class="keyword">from</span> <span class="string">'../components/globalHeader'</span>;</span><br><span class="line"></span><br><span class="line">Vue.use(createAntdDrawer, {</span><br><span class="line"> component: Drawer,</span><br><span class="line"> router, <span class="comment">// 子组件需要用到 this.$router 就传</span></span><br><span class="line"> store <span class="comment">// 子组件需要用到 this.$store 就传</span></span><br><span class="line"> globalHeader, <span class="comment">// 全局配置头部组件</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="创建抽屉"><a href="#创建抽屉" class="headerlink" title="创建抽屉"></a>创建抽屉</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$createAntdDrawer(options, arg1, arg2);</span><br><span class="line"><span class="keyword">this</span>.$createViewDrawer(options, arg1, arg2);</span><br><span class="line"><span class="keyword">this</span>.$createEleDrawer(options, arg1, arg2);</span><br></pre></td></tr></table></figure><h4 id="代码示例"><a href="#代码示例" class="headerlink" title="代码示例"></a>代码示例</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$createAntdDrawer({</span><br><span class="line"> drawerProps: {</span><br><span class="line"> title: <span class="string">'标题'</span>,</span><br><span class="line"> width: <span class="string">'500px'</span>,</span><br><span class="line"> mask: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"> content: {</span><br><span class="line"> template: HelloWorld,</span><br><span class="line"> props: {</span><br><span class="line"> msg: <span class="string">'Welcome to Your Vue.js App'</span>,</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> beforeClose: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'我要关闭了'</span>);</span><br><span class="line"> },</span><br><span class="line"> afterClose: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'我已经关闭了'</span>);</span><br><span class="line"> },</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="创建弹框"><a href="#创建弹框" class="headerlink" title="创建弹框"></a>创建弹框</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$createAntdModal(options, arg1, arg2);</span><br><span class="line"><span class="keyword">this</span>.$createViewModal(options, arg1, arg2);</span><br><span class="line"><span class="keyword">this</span>.$createEleModal(options, arg1, arg2);</span><br></pre></td></tr></table></figure><h4 id="代码示例-1"><a href="#代码示例-1" class="headerlink" title="代码示例"></a>代码示例</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$createAntdModal({</span><br><span class="line"> modalProps: {</span><br><span class="line"> title: <span class="string">'标题'</span>,</span><br><span class="line"> width: <span class="string">'500px'</span>,</span><br><span class="line"> mask: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"> content: {</span><br><span class="line"> template: HelloWorld,</span><br><span class="line"> props: {</span><br><span class="line"> msg: <span class="string">'Welcome to Your Vue.js App'</span>,</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> beforeClose: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'我要关闭了'</span>);</span><br><span class="line"> },</span><br><span class="line"> afterClose: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'我已经关闭了'</span>);</span><br><span class="line"> },</span><br><span class="line"> <span class="keyword">async</span> onOk() {</span><br><span class="line"> <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve</span>) =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'点了确定'</span>);</span><br><span class="line"> resolve(<span class="literal">false</span>);</span><br><span class="line"> }, <span class="number">3000</span>);</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> },</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="微前端中使用"><a href="#微前端中使用" class="headerlink" title="微前端中使用"></a>微前端中使用</h2><p>基于single-spa, vue-create-dm也支持在微前端项目中<strong>跨项目调用抽屉、弹框</strong></p><blockquote><h3 id="前提条件"><a href="#前提条件" class="headerlink" title="前提条件"></a>前提条件</h3><ol><li>需要互相调用抽屉、弹框的子项目<strong>必须</strong>都是 vue 技术栈</li><li>并且都安装注册了vue-create-dm</li><li>要跨项目调用的页面注册成路由页面</li></ol></blockquote><h3 id="示例项目"><a href="#示例项目" class="headerlink" title="示例项目"></a>示例项目</h3><p>参考 <a href="https://github.com/hzyhbk/vue-create-dm/tree/master/micro-frontend-example" title="微前端中使用vue-create-dm例子在线地址" target="_blank" rel="noopener">micro-frontend-example</a> 文件夹</p><h3 id="主项目使用"><a href="#主项目使用" class="headerlink" title="主项目使用"></a>主项目使用</h3><ol><li><p>注册子应用的方式需要改成config配置文件的方式。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> config = [{</span><br><span class="line"> name: <span class="string">'sub1'</span>,</span><br><span class="line"> app: <span class="function"><span class="params">()</span> =></span> loadSubApp(<span class="string">'sub1'</span>, <span class="string">'http://localhost:8081/manifest-initial.json'</span>),</span><br><span class="line"> activeWhen: <span class="function">(<span class="params">location</span>) =></span> location.pathname.startsWith(<span class="string">'/sub1'</span>),</span><br><span class="line"> customProps: {</span><br><span class="line"> domElement: <span class="string">'#app-sub-wrapper'</span>,</span><br><span class="line"> },</span><br><span class="line">}]</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> config;</span><br></pre></td></tr></table></figure></li><li><p>main.js加入如下代码</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { listenOpenDrawerAction, triggerOpenDrawerAction } <span class="keyword">from</span> <span class="string">'vue-create-dm'</span>;</span><br><span class="line"><span class="keyword">import</span> config <span class="keyword">from</span> <span class="string">'./micro-frontend/config/config'</span>;</span><br><span class="line"><span class="comment">// 监听打开抽屉事件</span></span><br><span class="line">listenOpenDrawerAction(config, <span class="string">'$createAntdDrawer'</span>);</span><br><span class="line"><span class="comment">// 子项目调用打开抽屉的函数</span></span><br><span class="line"><span class="built_in">window</span>.triggerOpenDrawerAction = triggerOpenDrawerAction;</span><br></pre></td></tr></table></figure></li></ol><h3 id="子项目使用"><a href="#子项目使用" class="headerlink" title="子项目使用"></a>子项目使用</h3><ol><li><p>除了 single-spa 要求导出的生命周期方法之外,子项目的<code>main.js</code>需要额外导出一个<code>router</code>实例和一个空的<code>vue</code>实例。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">'./router'</span>;</span><br><span class="line"><span class="keyword">import</span> store <span class="keyword">from</span> <span class="string">'./store'</span>;</span><br><span class="line"><span class="keyword">import</span> VueCreateDM <span class="keyword">from</span> <span class="string">'vue-create-dm'</span>;</span><br><span class="line"><span class="keyword">import</span> { Modal <span class="keyword">as</span> antdModal, Drawer <span class="keyword">as</span> antdDrawer } <span class="keyword">from</span> <span class="string">'ant-design-vue'</span>;</span><br><span class="line"></span><br><span class="line">Vue.use(VueCreateDM, {</span><br><span class="line"> antdDrawer,</span><br><span class="line"> antdModal,</span><br><span class="line"> router,</span><br><span class="line"> store,</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> bootstrap = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{...}</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> mount = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{...}</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> unmount = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{...}</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> update = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{...}</span><br><span class="line"><span class="comment">// 额外导出</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> $router = router;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> $Vue = <span class="keyword">new</span> Vue();</span><br></pre></td></tr></table></figure></li><li><p>需要被跨项目调用的组件,请在子项目的VueRouter路由配置文件中声明</p></li><li>子项目<strong>触发</strong><code>openDrawer</code>、<code>openModal</code>事件<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.triggerOpenDrawerAction({</span><br><span class="line"> appName: <span class="string">'sub2'</span>,</span><br><span class="line"> path: <span class="string">'/about'</span>,</span><br><span class="line"> drawerProps: {</span><br><span class="line"> title: <span class="string">'子应用二抽屉'</span>,</span><br><span class="line"> width: <span class="string">'50%'</span>,</span><br><span class="line"> },</span><br><span class="line"> content: {},</span><br><span class="line">});</span><br></pre></td></tr></table></figure></li></ol><p><br></p><p>PS: 如果觉得不错, 欢迎前往 Github <a href="https://github.com/hzyhbk/vue-create-dm" title="vue-create-dm Github地址" target="_blank" rel="noopener">vue-create-dm</a> 点个star</p><p>友情链接:</p><ul><li><a href="https://hzyhbk.github.io/vue-create-dm" title="vue-create-dm文档地址">vue-create-dm文档地址</a></li><li><a href="https://hzyhbk.github.io/react-create-dm" title="react-create-dm文档地址">react-create-dm文档地址</a></li></ul>]]></content>
<categories>
<category> vue </category>
</categories>
<tags>
<tag> javascript </tag>
<tag> vue </tag>
</tags>
</entry>
<entry>
<title>彻底理解Promise.then回调的执行顺序</title>
<link href="/start/learn-promise-then/"/>
<url>/start/learn-promise-then/</url>
<content type="html"><![CDATA[<p>前两天看一篇公众号文章<a href="https://mp.weixin.qq.com/s/ahIxhHWz4xTEEgEfdp_Pnw" target="_blank" rel="noopener">从一道面试题谈谈对 EventLoop 的理解</a>,里面讲到了JS的事件循环,开篇第一题和打怪进阶的第一题“黄金题”我还都会做,但是到了打怪进阶的第二题“砖石题”,我就懵了,怎么也想不通为什么。<br><a id="more"></a><br>然后开始查阅各种资料,看 Promise A+ 规范,也没搞懂为什么。这个问题在困扰了我两天之后,在昨晚和同事的交流中,终于得到了解答。归根结底还是我自己对规范理解的不够透彻。</p><p>所以下面先跟着我一起来重新理解一下<a href="https://promisesaplus.com/" target="_blank" rel="noopener">Promise A+ 规范</a>规范吧。</p><blockquote><p>A promise must provide a <code>then</code> method to access its current or eventual value or reason.<br>A promise’s <code>then</code> method accepts two arguments:</p><p>promise.then(onFulfilled, onRejected)</p><ul><li>2.2.2 If <code>onFulfilled</code> is a function:<ul><li>2.2.2.1 it must be called after <code>promise</code> is fulfilled, with <code>promise</code>’s value as its first argument.</li><li>2.2.2.2 it must not be called before <code>promise</code> is fulfilled.</li><li>2.2.2.3 it must not be called more than once.</li></ul></li></ul></blockquote><p>翻译一下就是: promise必须提供一个<code>then</code>方法来存取它当前或最终的值或者原因。promise的<code>then</code>方法接收两个参数:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">promise.then(onFulfilled, onRejected)</span><br></pre></td></tr></table></figure></p><p>如果 <code>onFulfilled</code> 是函数,<strong>此函数必须在promise 完成(fulfilled)后被调用,并把promise 的值作为它的第一个参数</strong>;此函数在<code>promise</code>完成(fulfilled)之前绝对不能被调用;此函数绝对不能被调用超过一次。</p><blockquote><ul><li>2.2.6 <code>then</code> may be called multiple times on the same promise.<ul><li>2.2.6.1 If/when <code>promise</code> is fulfilled, all respective <code>onFulfilled</code> callbacks must execute in the order of their originating calls to <code>then</code>.</li><li>2.2.6.2 If/when <code>promise</code> is rejected, all respective <code>onRejected</code> callbacks must execute in the order of their originating calls to <code>then</code>.</li></ul></li></ul></blockquote><p>翻译一下就是: <code>then</code> 在同一个promise里可以被调用多次,并且当promise的状态变为<code>fulfilled</code>或者<code>rejected</code>时,<code>onFulfilled</code>和<code>onRejected</code>回调函数的调用顺序将会按照在then里定义的顺序进行调用。</p><p>也就是像下面的代码那样:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> resolve();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">promise1.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">promise1.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><blockquote><ul><li>2.2.7 <code>then</code> must return a promise.</li></ul></blockquote><p>翻译一下就是: <code>then</code> 必须返回一个promise。也是因为这个规范,所以 promise 支持<strong>链式调用</strong>。<br><br>也就是像下面的代码那样:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise1 = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> resolve();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> promise2 = promise1.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> promise3 = promise2.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// or</span></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> resolve();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">})</span><br><span class="line">.then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>上面的代码打印顺序就是1,2,3。下面我们对代码来做一点改动:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> resolve();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function">(<span class="params">a</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"> resolve();</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">c</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">4</span>);</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">d</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">6</span>);</span><br><span class="line"> })</span><br><span class="line">})</span><br><span class="line">.then(<span class="function">(<span class="params">b</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">5</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>ok,结合前面理解的几条Promise A+规范,让我们来一起分析一下这段代码的执行顺序:</p><ul><li><p>首先打印 1 ,后面跟着个<code>resolve</code>调用,说明 promise 的状态变为<code>fulfilled</code>,所以把它的下一个 then 的回调即 <strong>a回调</strong> 放入微任务队列等待执行;<u>根据规范2.2.7,then必须返回一个promise</u> ,因此包含<strong>a回调</strong>的这个then返回了一个新的promise,我们记为<em>promise1</em> ,注意此时<strong>a回调</strong>还没被执行,也就是这个<em>promise1</em>的状态还是<code>pending</code>;<u>根据规范2.2.2.1,then回调的执行必须在上一个promise的状态为fulfilled</u>,所以下一个then,也就是<strong>b回调</strong>其实是被缓存在<em>promise1</em>内部的回调队列里,等promise1的状态改变再放入微任务队列。</p></li><li><p>接着执行<strong>a回调</strong>,先打印2,然后接着往下执行,遇到了一个新的 promise,我们记为<em>promise2</em>, 接着先打印3,然后后面跟着个<code>resolve</code>调用,说明这个<em>promise2</em>的状态变为<code>fulfilled</code>,所以把它的下一个 then 的回调也就是<strong>c回调</strong>放入微任务队列等待执行,同样<u>根据规范2.2.7</u>,包含c回调的这个then也返回了一个新的promise,我们记为<em>promise3</em>,此时c回调还没有执行,也就是这个<em>promise3</em>的状态还是<code>pending</code>,同样<u>根据规范2.2.2.1</u>,所以下一个then,也就是<strong>d回调</strong>其实是被缓存在<em>promise3</em>内部的回调队列里,等<em>promise3</em>的状态改变再放入微任务队列。</p></li><li><p>接着<strong>a回调</strong>执行完了,没有返回东西,可以理解为返回undefined ,<u>根据规范2.3.4,如果 x 既不是对象也不是函数,用x完成(fulfill)promise</u>,说明上面的<em>promise1</em>的状态变为了<code>fulfilled</code>,因此之前的<strong>b回调</strong>此时可以被放入微任务队列里等待执行了。</p></li><li><p>经过上面的步骤,此时微任务队列里存在<strong>c回调</strong>和<strong>b回调</strong>。</p></li><li><p>接着先执行<strong>c回调</strong>,打印4,<strong>c回调</strong>执行完成没有问题,<u>根据规范2.3.4</u>,也就是上面说到的<em>promise3</em>的状态变为了<code>fulfilled</code>,此时<strong>d回调</strong>可以被放入微任务队列等待执行了。</p></li><li><p>接着执行<strong>b回调</strong>,打印5。</p></li><li><p>接着执行<strong>d回调</strong>,打印6。</p></li></ul><p>综上所述,打印顺序为1,2,3,4,5,6。</p><p>理解了上面的执行顺序,我们再来稍微改变一下上面的代码,我们给内部的这个 promise 添加一个 return,来看看打印顺序会不会发生改变。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> resolve();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function">(<span class="params">a</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"> resolve();</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">c</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">4</span>);</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">d</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">6</span>);</span><br><span class="line"> })</span><br><span class="line">})</span><br><span class="line">.then(<span class="function">(<span class="params">b</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">5</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p><u>根据规范2.3.2,如果 <code>x</code> 是一个promise,采用promise的状态,如果 <code>x</code> 是请求状态(pending), <code>promise</code> (也就是这个then代表的promise)必须保持pending直到 <code>x</code> fulfilled 或 rejected;如果 <code>x</code> 是完成态(fulfilled),用相同的值完成fulfill <code>promise</code> ;如果 <code>x</code> 是拒绝态(rejected),用相同的原因reject <code>promise</code> 。</u></p><p>我的理解:此时<strong>包含a回调的这个then</strong>返回了一个新的promise,再链式调用的话,相当于<strong>包含b回调的这个then</strong>是被跟在返回的这个新的promise上。因此上面的代码可以直接理解为下面的代码:<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line"> resolve();</span><br><span class="line">})</span><br><span class="line">.then(<span class="function">(<span class="params">a</span>) =></span> {</span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line"> resolve();</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">c</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">4</span>);</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">d</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">6</span>);</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">b</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">5</span>);</span><br><span class="line"> });</span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>换成这段代码,再让你说出输出顺序就没问题了吧?答案是1,2,3,4,6,5。<br><br></p><p>参考文章:</p><ul><li><a href="https://promisesaplus.com/" target="_blank" rel="noopener">https://promisesaplus.com/</a></li><li><a href="https://juejin.im/post/6844903649852784647" target="_blank" rel="noopener">https://juejin.im/post/6844903649852784647</a></li><li><a href="https://blog.csdn.net/w993263495/article/details/88918458" target="_blank" rel="noopener">https://blog.csdn.net/w993263495/article/details/88918458</a></li><li><a href="https://yuchengkai.cn/docs/frontend/#promise-%E5%AE%9E%E7%8E%B0" target="_blank" rel="noopener">https://yuchengkai.cn/docs/frontend/#promise-%E5%AE%9E%E7%8E%B0</a></li></ul>]]></content>
<categories>
<category> js基础 </category>
</categories>
<tags>
<tag> javascript </tag>
<tag> promise </tag>
</tags>
</entry>
<entry>
<title>antd表格合并单元格,并且支持导出excel</title>
<link href="/start/export-excel/"/>
<url>/start/export-excel/</url>
<content type="html"><![CDATA[<p>最近接到个报表的需求,需要展示<strong>合并单元格的表格</strong>,并且还要支持<strong>导出excel</strong>。粗略一看,不就两个功能嘛,但是在实现的过程中发现其实要做的还是挺多的,所以在这里记录分享一下。</p><h2 id="一、合并单元格的实现"><a href="#一、合并单元格的实现" class="headerlink" title="一、合并单元格的实现"></a>一、合并单元格的实现</h2><a id="more"></a><p>合并单元格这个功能的实现用的是antd的table组件。下面是antd-table组件行列合并功能的使用介绍:</p><blockquote><p>表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。</p></blockquote><p>光看这句话好像还不太好理解,看下<a href="https://ant.design/components/table-cn/#components-table-demo-colspan-rowspan" target="_blank" rel="noopener">官方例子</a>的代码就好理解多了。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> title: <span class="string">'Name'</span>,</span><br><span class="line"> dataIndex: <span class="string">'name'</span>,</span><br><span class="line"> render: <span class="function">(<span class="params">text, row, index</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> obj = {</span><br><span class="line"> children: text,</span><br><span class="line"> props: {},</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">if</span> (index === <span class="number">2</span>) {</span><br><span class="line"> obj.props.rowSpan = <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// These two are merged into above cell</span></span><br><span class="line"> <span class="keyword">if</span> (index === <span class="number">3</span>) {</span><br><span class="line"> obj.props.rowSpan = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> obj;</span><br><span class="line"> },</span><br><span class="line">},</span><br></pre></td></tr></table></figure></p><p>我来解读一下上面这一项的意思:当 <code>index === 2</code>,把这一行的 <code>rowSpan</code> 设置为<strong>2</strong>,也就是第三条数据的时候,这一个单元格需要<strong>占两格</strong>,那么对应的它后面的一行,也就是 <code>index === 3</code> 这一行就只能占0格了,也就是需要把<code>index === 3</code>时候的 <code>rowSpan</code>设置为<strong>0</strong>。</p><p>了解了antd-table组件怎么设置单元格合并,下面就可以开始实现了。无非就是计算一下每一列里,每一项出现的次数,然后再设置下 <code>rowSpan</code> 的值就好了。但是在开始计算之前,还需要做一些准备工作:</p><ul><li>后端在返回数据的时候是通过 <em>树形结构</em> 返回的,而渲染表格用到的数据是 <em>数组</em> 的形式,所以我需要手动先转化一遍数据;</li><li>其次还要对数据按照表格每一列来排一次序,至于为什么排序,后面再说</li></ul><h3 id="1-处理原始数据"><a href="#1-处理原始数据" class="headerlink" title="1. 处理原始数据"></a>1. 处理原始数据</h3><p>后端返回的数据是这样子的:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> mockData = [{</span><br><span class="line"> children: [{</span><br><span class="line"> children: [{</span><br><span class="line"> children: [],</span><br><span class="line"> rate: <span class="number">0.3333333333333333</span>,</span><br><span class="line"> name: <span class="string">'需求1'</span>,</span><br><span class="line"> cost: <span class="number">3</span>,</span><br><span class="line"> id: <span class="number">5</span>,</span><br><span class="line"> projectId: <span class="number">1</span>,</span><br><span class="line"> projectName: <span class="string">'test1'</span>,</span><br><span class="line"> assigner: <span class="string">'张三'</span>,</span><br><span class="line"> }],</span><br><span class="line"> name: <span class="string">'迭代一'</span>,</span><br><span class="line"> rate: <span class="number">0.75</span>,</span><br><span class="line"> cost: <span class="number">9</span>,</span><br><span class="line"> id: <span class="number">2</span>,</span><br><span class="line"> }],</span><br><span class="line"> name: <span class="string">'项目一'</span>,</span><br><span class="line"> cost: <span class="number">12</span>,</span><br><span class="line"> id: <span class="number">1</span>,</span><br><span class="line">}];</span><br></pre></td></tr></table></figure></p><p><strong>上面的数据层级有三级,表示的含义就是表格至少会有三列,而且前三列的数据是需要做合并单元格操作的</strong>。树形结构数据首先就想到了用递归的方式,因此我需要通过递归来把这种类型的数据给<strong>拍平</strong>成数组,并且拿到每一级的信息。下面是我拍平之后的结果(firstColName、secondColName、thirdColName 就是表格的列标题,也可以是表格每一列的dataIndex字段,这里为了省事,我直接拿列标题来用了)。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> flatMockData = [</span><br><span class="line"> {</span><br><span class="line"> firstColName: {</span><br><span class="line"> name: <span class="string">'项目一'</span>,</span><br><span class="line"> cost: <span class="number">12</span>,</span><br><span class="line"> id: <span class="number">1</span>,</span><br><span class="line"> children: <span class="comment">//...</span></span><br><span class="line"> },</span><br><span class="line"> secondColName: {</span><br><span class="line"> name: <span class="string">'迭代一'</span>,</span><br><span class="line"> rate: <span class="number">0.75</span>,</span><br><span class="line"> cost: <span class="number">9</span>,</span><br><span class="line"> id: <span class="number">2</span>,</span><br><span class="line"> children: <span class="comment">//...</span></span><br><span class="line"> },</span><br><span class="line"> thirdColName: {</span><br><span class="line"> rate: <span class="number">0.3333333333333333</span>,</span><br><span class="line"> name: <span class="string">'需求一'</span>,</span><br><span class="line"> cost: <span class="number">3</span>,</span><br><span class="line"> id: <span class="number">5</span>,</span><br><span class="line"> projectId: <span class="number">1</span>,</span><br><span class="line"> projectName: <span class="string">'test1'</span>,</span><br><span class="line"> assigner: <span class="string">'张三'</span>,</span><br><span class="line"> children: <span class="comment">//...,</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line">]</span><br></pre></td></tr></table></figure></p><p>要让 <strong>树形结构每一级</strong> 和 <strong>表格的每一列</strong> 对应起来,原始数据肯定是不够的,还需要额外一个参数来表示每一级的深度,这样我就能把每一级的数据和表格列名对应起来。</p><h4 id="1-1-树形结构数据加上层级参数"><a href="#1-1-树形结构数据加上层级参数" class="headerlink" title="1.1 树形结构数据加上层级参数"></a>1.1 树形结构数据加上层级参数</h4><p>这一步很好实现,一个递归就好了,直接上代码。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addDeepsToTreeData</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> data: ITreeDataItem[],</span></span></span><br><span class="line"><span class="function"><span class="params"> depsNum: number = <span class="number">0</span>,</span></span></span><br><span class="line">): ITreeDataDepsItem[] {</span><br><span class="line"> <span class="keyword">return</span> data.map(<span class="function"><span class="params">item</span> =></span> ({</span><br><span class="line"> ...item,</span><br><span class="line"> __deps: depsNum, <span class="comment">// 表示层级的参数</span></span><br><span class="line"> children: addDeepsToTreeData(item.children, depsNum + <span class="number">1</span>),</span><br><span class="line"> }));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h4 id="1-2-拍平树形结构数据"><a href="#1-2-拍平树形结构数据" class="headerlink" title="1.2 拍平树形结构数据"></a>1.2 拍平树形结构数据</h4><p>因为之前我们已经给树形结构的每一层都加了表示层级的参数,这样做就是为了在拍平数据这一步的时候能很快和表格的列名对应起来。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> colNameList = [<span class="string">'firstColName'</span>, <span class="string">'secondColName'</span>, <span class="string">'thirdColName'</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFlatData</span>(<span class="params">data: ITreeDepsDataItem[], colNameList: string[]</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> array: { [k: string]: ITreeDepsDataItem }[] = [];</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">convert</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> data: ITreeDepsDataItem[],</span></span></span><br><span class="line"><span class="function"><span class="params"> parentItem: { [k: string]: any } = {}, <span class="regexp">//</span> 上一级的信息</span></span></span><br><span class="line"><span class="function"><span class="params"> </span>) </span>{</span><br><span class="line"> _.forEach(data, item => {</span><br><span class="line"> <span class="keyword">if</span> (item.children && item.children.length !== <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (parentItem) {</span><br><span class="line"> <span class="comment">// 如果自己的children不为空,并且有上几级信息</span></span><br><span class="line"> <span class="comment">// 就加上自己这一级的信息,继续往下传递</span></span><br><span class="line"> convert(item.children, {</span><br><span class="line"> ...parentItem,</span><br><span class="line"> [colNameList[item.__deps]]: item,</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果自己的children不为空 ,但是没有上几级的信息没有上一级信息</span></span><br><span class="line"> <span class="comment">// 就把自己这一级的信息传递下去</span></span><br><span class="line"> convert(item.children, { [colNameList[item.__deps]]: item });</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// children为空说明走到最后一级了</span></span><br><span class="line"> <span class="comment">// 这时,表格一行里所有列的数据都获取到了,就push到临时数组里</span></span><br><span class="line"> array.push({ ...parentItem,[colNameList[item.__deps]]: item, });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> convert(data);</span><br><span class="line"> <span class="keyword">return</span> array;</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h4 id="1-3-排序拍平之后的数组"><a href="#1-3-排序拍平之后的数组" class="headerlink" title="1.3 排序拍平之后的数组"></a>1.3 排序拍平之后的数组</h4><p>比如最后的表格有三列需要合并单元格,每一行就要根据这三列综合来排序;最后的表格有n列需要合并单元格,那么每一行就要根据这n列综合来排序,这样做是为了保证合并的都是重复出现的单元格。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取单个条件的排序函数</span></span><br><span class="line">type BooleanFn<T> = <span class="function">(<span class="params">x: T, y: T</span>) =></span> boolean;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getSort</span><<span class="title">T</span>>(<span class="params">fn: BooleanFn<T></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">a: T, b: T</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> ret = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (fn.call(<span class="keyword">this</span>, a, b)) {</span><br><span class="line"> ret = <span class="number">-1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (fn.call(<span class="keyword">this</span>, b, a)) {</span><br><span class="line"> ret = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取多个条件的排序函数</span></span><br><span class="line">type NumberFn<T> = <span class="function">(<span class="params">x: T, y: T</span>) =></span> number;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getMutipSort</span><<span class="title">T</span>>(<span class="params">arr: NumberFn<T>[]</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">a: T, b: T</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> tmp, i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> tmp = arr[i++](a, b);</span><br><span class="line"> } <span class="keyword">while</span> (tmp == <span class="number">0</span> && i < arr.length);</span><br><span class="line"> <span class="keyword">return</span> tmp;</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据多个条件排序数据</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getSortableData</span>(<span class="params">flatData: { [k: string]: ITreeDepsDataItem }[],colNameList: string[]</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> sortableData = _.cloneDeep(flatData);</span><br><span class="line"> <span class="keyword">const</span> sortArr = colNameList.map(<span class="function"><span class="params">item</span> =></span></span><br><span class="line"> getSort<{ [k: string]: ITreeDepsDataItem }><span class="function">(<span class="params">(a, b</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (a[item] && b[item]) {</span><br><span class="line"> <span class="keyword">return</span> a[item].name.toUpperCase() < b[item].name.toUpperCase();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }),</span><br><span class="line"> );</span><br><span class="line"> sortableData.sort(getMutipSort(sortArr));</span><br><span class="line"> <span class="keyword">return</span> sortableData;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>到这里,数据就已经处理好了。下面就是计算每一行下面每一个单元格出现的次数</p><h3 id="2-计算每一行里每个单元格出现的次数"><a href="#2-计算每一行里每个单元格出现的次数" class="headerlink" title="2. 计算每一行里每个单元格出现的次数"></a>2. 计算每一行里每个单元格出现的次数</h3><p>循环排好序的数据,计算一行里所有单元格的重复次数(以 <code>${name}_${id}</code> 作为数据的唯一标识,只用 <code>id</code> 做唯一标识也可以),并把结果存在以 <strong>这一行里第一个单元格数据的唯一标识</strong> 作为key的对象中。这句话可能有点不好理解,所以我用下面的数据来解释下<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设这是已经排好序的数据</span></span><br><span class="line"><span class="keyword">const</span> sortableData = [{</span><br><span class="line"> firstColName: { <span class="attr">name</span>: <span class="string">'项目一'</span>, <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">cost</span>: <span class="number">12</span>, <span class="attr">children</span>: [] },</span><br><span class="line"> secondColName: { <span class="attr">name</span>: <span class="string">'迭代一'</span>, <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">rate</span>: <span class="number">0.75</span>, <span class="attr">cost</span>: <span class="number">9</span>, <span class="attr">children</span>: [] },</span><br><span class="line"> thirdColName: { <span class="attr">name</span>: <span class="string">'需求一'</span>, <span class="attr">id</span>: <span class="number">5</span>, <span class="attr">rate</span>: <span class="number">0.25</span>, <span class="attr">cost</span>: <span class="number">3</span>, <span class="attr">projectId</span>: <span class="number">1</span>, <span class="attr">projectName</span>: <span class="string">'项目一'</span>, <span class="attr">assigner</span>: <span class="string">'张三'</span>, <span class="attr">children</span>: [], }</span><br><span class="line">},{</span><br><span class="line"> firstColName: { <span class="attr">name</span>: <span class="string">'项目一'</span>, <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">cost</span>: <span class="number">12</span>, <span class="attr">children</span>: [] },</span><br><span class="line"> secondColName: { <span class="attr">name</span>: <span class="string">'迭代一'</span>, <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">rate</span>: <span class="number">0.75</span>, <span class="attr">cost</span>: <span class="number">9</span>, <span class="attr">children</span>: [] },</span><br><span class="line"> thirdColName: { <span class="attr">name</span>: <span class="string">'需求二'</span>, <span class="attr">id</span>: <span class="number">6</span>, <span class="attr">rate</span>: <span class="number">0.25</span>, <span class="attr">cost</span>: <span class="number">3</span>, <span class="attr">projectId</span>: <span class="number">1</span>, <span class="attr">projectName</span>: <span class="string">'项目一'</span>, <span class="attr">assigner</span>: <span class="string">'李四'</span>, <span class="attr">children</span>: [], }</span><br><span class="line">}];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算之后的重复次数就是这样</span></span><br><span class="line"><span class="comment">// 这个重复出现的次数就是后面 render 函数返回的 rowSpan 的值。</span></span><br><span class="line"><span class="keyword">const</span> cellRepetitions = {</span><br><span class="line"> 项目一_1: {</span><br><span class="line"> 项目一_1: <span class="number">2</span>,</span><br><span class="line"> 迭代一_2: <span class="number">2</span>,</span><br><span class="line"> 需求一_5: <span class="number">1</span>,</span><br><span class="line"> 需求二_6: <span class="number">1</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>下面是计算重复次数的函数<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取每一组数据里重复出现的数据的次数</span></span><br><span class="line">type IObjNumber = { [k: string]: number };</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getCellRepetitions</span>(<span class="params">sortableData:{ [k: string]: ITreeDepsDataItem }[], colNameList: string[]</span>) </span>{</span><br><span class="line"> <span class="comment">// 表格第一列的列名</span></span><br><span class="line"> <span class="keyword">const</span> parentColName = colNameList[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">const</span> cellRepetitions: {</span><br><span class="line"> [k: string]: IObjNumber | number;</span><br><span class="line"> } = {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 循环排好序的数据,item代表每一行</span></span><br><span class="line"> _.forEach(sortableData, item => {</span><br><span class="line"> <span class="keyword">const</span> parentColVal = <span class="string">`<span class="subst">${item[parentColName].name}</span>_<span class="subst">${item[parentColName].id}</span>`</span>;</span><br><span class="line"> <span class="keyword">if</span> (!cellRepetitions[parentColVal]) {</span><br><span class="line"> cellRepetitions[parentColVal] = {};</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 循环列名,item[col]代表这一行里每一个单元格</span></span><br><span class="line"> _.forEach(colNameList, col => {</span><br><span class="line"> <span class="keyword">if</span> (item[col]) {</span><br><span class="line"> <span class="keyword">const</span> colValue = <span class="string">`<span class="subst">${item[col].name}</span>_<span class="subst">${item[col].id}</span>`</span>;</span><br><span class="line"> cellRepetitions[<span class="string">`__dot_<span class="subst">${col}</span>`</span>] = <span class="number">0</span>; <span class="comment">//后面在render的时候会用到的标志位</span></span><br><span class="line"> <span class="keyword">if</span> ((cellRepetitions[parentColVal] <span class="keyword">as</span> IObjNumber)[colValue]) {</span><br><span class="line"> (cellRepetitions[parentColVal] <span class="keyword">as</span> IObjNumber)[colValue]++;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> (cellRepetitions[parentColVal] <span class="keyword">as</span> IObjNumber)[colValue] = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> cellRepetitions;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="3-render函数"><a href="#3-render函数" class="headerlink" title="3. render函数"></a>3. render函数</h3><p>计算好了重复次数之后,下面就可以写列的 <code>render</code> 函数了。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 以下是使用自己封装的 renderContent 的例子</span></span><br><span class="line"><span class="comment">// renderContent 函数接收三个参数</span></span><br><span class="line"><span class="comment">// 第一个参数是 antd-table 原始render函数的所有参数</span></span><br><span class="line"><span class="comment">// 第二个参数是列名</span></span><br><span class="line"><span class="comment">// 第三个参数为可选参数,不传的话默认返回name字段的值,传了的话就可以自定义返回内容</span></span><br><span class="line">{</span><br><span class="line"> name: <span class="string">'colName'</span>,</span><br><span class="line"> dataIndex: <span class="string">'colName'</span>,</span><br><span class="line"> render: <span class="function">(<span class="params">...rest</span>) =></span> renderContent(rest, <span class="string">'colName'</span>),</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取表格每项的render函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getRenderContent</span>(<span class="params">cellRepetitions: { [k: string]: IObjNumber | number; }, colNameList: string[]</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> parentColName = colNameList[<span class="number">0</span>];</span><br><span class="line"> <span class="comment">// 这里需要实时修改前面留下的 __dot标志位,所以先拷贝一份数据</span></span><br><span class="line"> <span class="keyword">const</span> countObj = _.cloneDeep(cellRepetitions);</span><br><span class="line"> <span class="keyword">const</span> renderContent: IRenderContent = (</span><br><span class="line"> rest: any,</span><br><span class="line"> name: string,</span><br><span class="line"> userRenderText?: IRenderText, <span class="comment">// 自定义表格返回什么格式的数据</span></span><br><span class="line"> ) => {</span><br><span class="line"> <span class="keyword">const</span> [v, row, index] = rest;</span><br><span class="line"> <span class="keyword">const</span> textForRender = userRenderText ? userRenderText(row) : v.name;</span><br><span class="line"> <span class="keyword">const</span> obj: {</span><br><span class="line"> children: string;</span><br><span class="line"> props: { rowSpan?: number };</span><br><span class="line"> } = {</span><br><span class="line"> children: textForRender,</span><br><span class="line"> props: {},</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 有 v 说明是需要合并的列</span></span><br><span class="line"> <span class="keyword">if</span> (v) {</span><br><span class="line"> <span class="keyword">const</span> value = <span class="string">`<span class="subst">${v.name}</span>_<span class="subst">${v.id}</span>`</span>;</span><br><span class="line"> <span class="keyword">const</span> parentColVal = <span class="string">`<span class="subst">${row[parentColName].name}</span>_<span class="subst">${row[parentColName].id}</span>`</span>;</span><br><span class="line"> <span class="keyword">if</span> (countObj[parentColVal]) {</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * index表示第几行的数据</span></span><br><span class="line"><span class="comment"> * 当 行数 等于 这一列的标志位 的时候,说明这一个单元格是需要被显示的</span></span><br><span class="line"><span class="comment"> * 所以把这一个单元格的 rowspan 设置为它的重复次数</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 并且这时更新标志位的数值,加上当前单元格的重复次数</span></span><br><span class="line"><span class="comment"> * 后面当 行数 小于 这一列标志位 的时候,说明这一个单元格是需要被隐藏的,</span></span><br><span class="line"><span class="comment"> * 这时把 rowSpan 设置为 0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (index === countObj[<span class="string">`__dot_<span class="subst">${name}</span>`</span>]) {</span><br><span class="line"> obj.props.rowSpan = (countObj[parentColVal] <span class="keyword">as</span> IObjNumber)[value];</span><br><span class="line"> (countObj[<span class="string">`__dot_<span class="subst">${name}</span>`</span>] <span class="keyword">as</span> number) += (countObj[</span><br><span class="line"> parentColVal</span><br><span class="line"> ] <span class="keyword">as</span> IObjNumber)[value];</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (index < countObj[<span class="string">`__dot_<span class="subst">${name}</span>`</span>]) {</span><br><span class="line"> obj.props.rowSpan = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> obj;</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> renderContent;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上,终于把表格合并单元格功能给完成了。下面就可以愉快的开始想怎么解决导出excel了。</p><h2 id="二、-导出excel功能的实现"><a href="#二、-导出excel功能的实现" class="headerlink" title="二、 导出excel功能的实现"></a>二、 导出excel功能的实现</h2><p>导出excel功能使用的是 <a href="https://github.com/SheetJS/sheetjs" target="_blank" rel="noopener">SheetJS</a> 这个库。</p><p>并且参考了这位大佬的文章 <a href="http://blog.haoji.me/js-excel.html" target="_blank" rel="noopener">http://blog.haoji.me/js-excel.html</a></p><p>遇到的坑点就是,js-xlsx 默认不支持设置样式。但是经过一番查找,发现了一个叫 <a href="https://github.com/protobi/js-xlsx" target="_blank" rel="noopener">xlsx-style</a> 的库,使用这个库就可以给导出的excel设置样式。</p><h3 id="通过dom节点导出"><a href="#通过dom节点导出" class="headerlink" title="通过dom节点导出"></a>通过dom节点导出</h3><p>具体的使用方式如下:</p><ol><li>下载 <code>js-xlsx/dist/xlsx.full.min.js</code> 到项目中;</li><li>下载 <code>xlsx-style/dist/xlsx.full.min.js</code> 到项目中;</li><li>修改 <code>xlsx-style/dist/xlsx.full.min.js</code> 中的 <code>XLSX</code> 变量 为<code>XLSX_STYLE</code>,因为两个文件都是默认设置全局变量 <strong>XLSX</strong>,而我们只需要在最后导出的时候使用 xlsx-style 提供的方法,而其他工具方法还是使用 js-xlsx 中的;</li><li>在项目的index.html中引入这两个文件;</li><li><p>js-xlsx 支持从dom节点导出excel,所以方便起见直接用dom节点导出。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> dom = <span class="built_in">document</span>.querySelector(<span class="string">'.ant-table-body'</span>);</span><br><span class="line"><span class="keyword">const</span> sheets = XLSX.utils.table_to_book(dom).Sheets.Sheet1;</span><br></pre></td></tr></table></figure></li><li><p>给 sheet 设置样式,下面给出我的设置作为参考,更多设置请自行参考 <a href="https://github.com/protobi/js-xlsx#cell-styles" target="_blank" rel="noopener">xlsx-style#cell-styles</a></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setSheetStyle</span>(<span class="params">sheet: ISheet, colNameList:string[]</span>) </span>{</span><br><span class="line"> <span class="comment">// 设置列宽</span></span><br><span class="line"> sheet[<span class="string">'!cols'</span>] = colNameList.map(<span class="function"><span class="params">item</span> =></span> ({ <span class="attr">wpx</span>: <span class="number">200</span> }));</span><br><span class="line"> <span class="built_in">Object</span>.keys(sheet).forEach(<span class="function"><span class="params">key</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> sheet[key] === <span class="string">'object'</span>) {</span><br><span class="line"> <span class="comment">// 第一行是标题行</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/^[A-Z]+1$/</span>.test(key)) {</span><br><span class="line"> sheet[key].s = {</span><br><span class="line"> font: {</span><br><span class="line"> sz: <span class="number">18</span>, <span class="comment">// 字体大小为18px</span></span><br><span class="line"> bold: <span class="literal">true</span>,<span class="comment">// 加粗</span></span><br><span class="line"> },</span><br><span class="line"> alignment: {</span><br><span class="line"> vertical: <span class="string">'center'</span>, <span class="comment">// 垂直居中</span></span><br><span class="line"> wrapText: <span class="literal">true</span>, <span class="comment">// 自动换行</span></span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> sheet[key].s = {</span><br><span class="line"> alignment: {</span><br><span class="line"> vertical: <span class="string">'center'</span>,</span><br><span class="line"> wrapText: <span class="literal">true</span>,</span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>在 sheet2blob 这个方法中 调用 <strong><code>XLSX_STYLE.write</code></strong> 方法来创建 workbook,上面一步设置的样式才会生效。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sheet2blob</span>(<span class="params">sheet: ISheet, name?: string</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> sheetName = name || <span class="string">'sheet1'</span>;</span><br><span class="line"> <span class="keyword">const</span> workbook: {</span><br><span class="line"> SheetNames: string[];</span><br><span class="line"> Sheets: { [k: string]: any }; <span class="comment">//多个sheet</span></span><br><span class="line"> } = {</span><br><span class="line"> SheetNames: [sheetName],</span><br><span class="line"> Sheets: {},</span><br><span class="line"> };</span><br><span class="line"> workbook.Sheets[sheetName] = sheet;</span><br><span class="line"> <span class="comment">// 生成excel的配置项</span></span><br><span class="line"> <span class="keyword">var</span> wopts = {</span><br><span class="line"> bookType: <span class="string">'xlsx'</span>, <span class="comment">// 要生成的文件类型</span></span><br><span class="line"> bookSST: <span class="literal">false</span>, <span class="comment">// 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性</span></span><br><span class="line"> type: <span class="string">'binary'</span>,</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 这里调用 XLSX_STYLE 的 write 方法</span></span><br><span class="line"> <span class="keyword">const</span> wbout = XLSX_STYLE.write(workbook, wopts);</span><br><span class="line"> <span class="keyword">const</span> blob = <span class="keyword">new</span> Blob([s2ab(wbout)], { <span class="attr">type</span>: <span class="string">'application/octet-stream'</span> });</span><br><span class="line"> <span class="comment">// 字符串转ArrayBuffer</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">s2ab</span>(<span class="params">s: any</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> buf = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(s.length);</span><br><span class="line"> <span class="keyword">const</span> view = <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(buf);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i != s.length; ++i) view[i] = s.charCodeAt(i) & <span class="number">0xff</span>;</span><br><span class="line"> <span class="keyword">return</span> buf;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> blob;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>导出excel文件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 通用的打开下载对话框方法,没有测试过具体兼容性</span></span><br><span class="line"><span class="comment"> * @param url 下载地址,也可以是一个blob对象,必选</span></span><br><span class="line"><span class="comment"> * @param saveName 保存文件名,可选</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">openDownloadDialog</span>(<span class="params">url: string | Blob, saveName?: string</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> url == <span class="string">'object'</span> && url <span class="keyword">instanceof</span> Blob) {</span><br><span class="line"> url = URL.createObjectURL(url); <span class="comment">// 创建blob地址</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">let</span> aLink = <span class="built_in">document</span>.createElement(<span class="string">'a'</span>);</span><br><span class="line"> aLink.href = url;</span><br><span class="line"> aLink.download = saveName || <span class="string">''</span>; <span class="comment">// HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效</span></span><br><span class="line"> <span class="keyword">let</span> event;</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">window</span>.MouseEvent) {</span><br><span class="line"> event = <span class="keyword">new</span> MouseEvent(<span class="string">'click'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> event = <span class="built_in">document</span>.createEvent(<span class="string">'MouseEvents'</span>);</span><br><span class="line"> event.initMouseEvent(</span><br><span class="line"> <span class="string">'click'</span>,</span><br><span class="line"> <span class="literal">true</span>,</span><br><span class="line"> <span class="literal">false</span>,</span><br><span class="line"> <span class="built_in">window</span>,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="literal">false</span>,</span><br><span class="line"> <span class="literal">false</span>,</span><br><span class="line"> <span class="literal">false</span>,</span><br><span class="line"> <span class="literal">false</span>,</span><br><span class="line"> <span class="number">0</span>,</span><br><span class="line"> <span class="literal">null</span>,</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"> aLink.dispatchEvent(event);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">downloadExcelWithDom</span>(<span class="params">dom: HTMLTableElement, saveName?: string</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> fileName = <span class="string">`<span class="subst">${saveName}</span>.xlsx`</span> || <span class="string">'导出.xlsx'</span>;</span><br><span class="line"> <span class="keyword">const</span> sheet = XLSX.utils.table_to_book(dom).Sheets.Sheet1;</span><br><span class="line"> setSheetStyle(sheet);</span><br><span class="line"> openDownloadDialog(sheet2blob(sheet), fileName);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><p>以上就完成了导出excel功能。由于兼容性问题,有些浏览器上可能无法通过dom节点导出,这时候就只能自己设置数据来导出了。下面也讲讲怎么通过自己设置数据来导出。</p><h3 id="通过数据导出"><a href="#通过数据导出" class="headerlink" title="通过数据导出"></a>通过数据导出</h3><ol><li><p>使用 <code>XLSX.utils.aoa_to_sheet</code> 方法来生成sheet,这个方法可以将一个二维数组转成sheet格式</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getSheetData</span>(<span class="params">sortableData:{ [k: string]: ITreeDepsDataItem }[], colNameList: string[]</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> aoa: string[][] = [];</span><br><span class="line"> aoa.push(colNameList);</span><br><span class="line"> sortableData.forEach(<span class="function"><span class="params">item</span> =></span> {</span><br><span class="line"> aoa.push(</span><br><span class="line"> <span class="comment">// 这里只是简单的把单元格的内容设置为name属性</span></span><br><span class="line"> <span class="comment">// 如果需要自定义单元格内容的话,可以使用 table 组件里设置好的 columns</span></span><br><span class="line"> colNameList.map(<span class="function"><span class="params">colName</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (item[colName]) {</span><br><span class="line"> <span class="keyword">return</span> item[colName].name;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">const</span> lastColName = colNameList[<span class="built_in">Object</span>.keys(item).length - <span class="number">1</span>];</span><br><span class="line"> <span class="keyword">return</span> (item[lastColName] <span class="keyword">as</span> any)[colName] || <span class="string">'-'</span>;</span><br><span class="line"> }</span><br><span class="line"> }),</span><br><span class="line"> );</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> XLSX.utils.aoa_to_sheet(aoa);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>循环表格的列跟行,计算需要合并的单元格,核心逻辑和和前面说的 <code>renderContent</code> 方法是相同的。单元格的合并需要设置 <code>sheet[!merges]</code>,格式如下</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 表示从 第0行第0列 到 第0行第2列 的单元格合并</span></span><br><span class="line"><span class="comment">// 也就是第0行前三个单元格合并</span></span><br><span class="line">sheet[!merges] = [</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 表示start=</span></span><br><span class="line"> s:{</span><br><span class="line"> r: <span class="number">0</span>, <span class="comment">// 表示row</span></span><br><span class="line"> c: <span class="number">0</span>, <span class="comment">// 表示col的</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 表示end</span></span><br><span class="line"> e:{</span><br><span class="line"> r: <span class="number">0</span>,</span><br><span class="line"> c: <span class="number">2</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>下面是获取每一行需要合并的s和e</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">_.forEach(colNameList, (item, colIndex) => {</span><br><span class="line"> _.forEach(sortableData, (row, rowIndex) => {</span><br><span class="line"> <span class="comment">// getRepeatNum 方法同 renderContent,repeat就相当于rowSpan</span></span><br><span class="line"> <span class="keyword">const</span> repeat = getRepeatNum(row[item], row, rowIndex, item);</span><br><span class="line"> <span class="comment">// 只对 repeat 大于1 的单元格处理</span></span><br><span class="line"> <span class="comment">// 并且这里列是固定的,只有行会发生合并,所以会简单一点</span></span><br><span class="line"> <span class="keyword">if</span> (repeat > <span class="number">1</span>) {</span><br><span class="line"> sheetMerges.push({</span><br><span class="line"> s: {</span><br><span class="line"> r: rowIndex + <span class="number">1</span>, <span class="comment">// 多了标题栏 所以要加 1</span></span><br><span class="line"> c: colIndex,</span><br><span class="line"> },</span><br><span class="line"> e: {</span><br><span class="line"> r: rowIndex + repeat - <span class="number">1</span> + <span class="number">1</span>, <span class="comment">// 多了标题栏 所以要加 1</span></span><br><span class="line"> c: colIndex,</span><br><span class="line"> },</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure></li><li><p>最后 设置样式 和 导出 这两步和上面通过dom节点导出一样,不再赘述。</p></li></ol>]]></content>
<categories>
<category> web前端 </category>
</categories>
<tags>
<tag> antd-table </tag>
<tag> js-xlsx </tag>
<tag> react </tag>
</tags>
</entry>
<entry>
<title>2020年元旦快乐</title>
<link href="/start/happy-2020/"/>
<url>/start/happy-2020/</url>
<content type="html"><![CDATA[<p>2020年元旦快乐!</p><p>2020年第一天,给博客换了个logo,顺便分享一件早上发生的糗事。</p><a id="more"></a><p>早上起床,看外面有太阳,于是就想拿被子去顶楼晒一晒。我住11楼,顶楼在14楼。起床,扛上被子,进电梯,按14楼没反应,猛地反应过来电梯从今天起启用梯控卡。可是我没带,只好尴尬的随电梯到了一楼。在一楼扛着被子等了一会,没等到人上楼,迫不得已,自己扛着被子爬上了11楼…</p>]]></content>
<categories>
<category> 生活 </category>
</categories>
</entry>
<entry>
<title>echarts-for-react 修改点击图例事件</title>
<link href="/start/echarts-for-react%E4%BF%AE%E6%94%B9%E7%82%B9%E5%87%BB%E5%9B%BE%E4%BE%8B%E4%BA%8B%E4%BB%B6/"/>
<url>/start/echarts-for-react%E4%BF%AE%E6%94%B9%E7%82%B9%E5%87%BB%E5%9B%BE%E4%BE%8B%E4%BA%8B%E4%BB%B6/</url>
<content type="html"><![CDATA[<p><strong>需求</strong>:点击图例,则选中该项,将其他项置灰;再次点击,恢复选中所有选项</p><p><strong>做法</strong>:监听下<code>legendselectchanged</code>事件,用一个变量来保存上次点击的图例的名字,</p><ul><li>如果点击的图例名称和保存的一致,说明是再次点击,应该恢复所有选项,把所有图例都置为 true;</li><li>如果点击的图例名称和保存的不一致,说明点击了另外的图例,这是需要更新变量,并且把这次点击的图例的selected设置为true,其他的设置为false。</li></ul><p><strong>坑点</strong>:如果在<code>legendselectchanged</code>里调用 echarts 的 <a href="https://www.echartsjs.com/zh/api.html#action.legend.legendToggleSelect" target="_blank" rel="noopener">dispatchActions</a>,会陷入循环调用,如果还是想调用 <code>dispatchActions</code>方法,可参考这篇<a href="https://segmentfault.com/q/1010000014133645" target="_blank" rel="noopener">文章</a>。我就直接调用setOption了。</p><p>监听 legendselectchanged 事件代码如下:<br><a id="more"></a><br><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">onLegendSelectChanged = (e: {</span><br><span class="line"> name: string;</span><br><span class="line"> selected: { [k: string]: boolean };</span><br><span class="line"> }) => {</span><br><span class="line"> <span class="keyword">let</span> selected = <span class="built_in">Object</span>.assign({}, e.selected);</span><br><span class="line"> <span class="comment">// 用一个变量来保存上次点击的图例的名字</span></span><br><span class="line"> <span class="comment">// 如果新点击的图例名称和保存的一致,说明是再次点击,应该恢复所有选项,</span></span><br><span class="line"> <span class="comment">// 就是把所有图例都置为true</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.clickedLegendName === e.name) {</span><br><span class="line"> <span class="keyword">this</span>.clickedLegendName = <span class="string">''</span>;</span><br><span class="line"> <span class="built_in">Object</span>.keys(selected).forEach(<span class="function"><span class="params">key</span> =></span> {</span><br><span class="line"> selected[key] = <span class="literal">true</span>;</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果和保存的名称不一致,说明点击了另外的图例</span></span><br><span class="line"> <span class="comment">// 更新clickedLegendName</span></span><br><span class="line"> <span class="comment">// 把这次点击的图例的selected设置为true,其他的设置为false</span></span><br><span class="line"> <span class="keyword">this</span>.clickedLegendName = e.name;</span><br><span class="line"> <span class="built_in">Object</span>.keys(selected).forEach(<span class="function"><span class="params">key</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (key === e.name) {</span><br><span class="line"> selected[key] = <span class="literal">true</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> selected[key] = <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 重新调用 getEchartsOption</span></span><br><span class="line"> <span class="keyword">const</span> option = <span class="keyword">this</span>.getEchartsOption(selected);</span><br><span class="line"> <span class="comment">// 重新调用 setOption</span></span><br><span class="line"> <span class="keyword">this</span>.echartRef.setOption(option);</span><br><span class="line"> };</span><br></pre></td></tr></table></figure></p><p><br><hr><br></p><p>组件完整代码如下:<br><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, { PureComponent } <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">'lodash'</span>;</span><br><span class="line"><span class="keyword">import</span> styled <span class="keyword">from</span> <span class="string">'styled-components'</span>;</span><br><span class="line"><span class="keyword">import</span> ReactEcharts <span class="keyword">from</span> <span class="string">'echarts-for-react'</span>;</span><br><span class="line"><span class="keyword">import</span> moment <span class="keyword">from</span> <span class="string">'moment'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Root = styled.div<span class="string">``</span>;</span><br><span class="line"></span><br><span class="line">type ILineChartProps = {</span><br><span class="line"> className?: string;</span><br><span class="line"> style?: React.CSSProperties;</span><br><span class="line"> rawData: {</span><br><span class="line"> data: IRawChartDataItem[];</span><br><span class="line"> series: {</span><br><span class="line"> name: string;</span><br><span class="line"> data: number[];</span><br><span class="line"> type: <span class="string">'line'</span>;</span><br><span class="line"> areaStyle: {};</span><br><span class="line"> }[];</span><br><span class="line"> };</span><br><span class="line"> loading: boolean;</span><br><span class="line">};</span><br><span class="line">type ILineChartState = {};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LineChart</span> <span class="keyword">extends</span> <span class="title">PureComponent</span><<span class="title">ILineChartProps</span>, <span class="title">ILineChartState</span>> </span>{</span><br><span class="line"> echartRef: any = <span class="literal">null</span>;</span><br><span class="line"> clickedLegendName: string;</span><br><span class="line"></span><br><span class="line"> getEchartsOption = <span class="function">(<span class="params">selected: { [k: string]: boolean } = {}</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> {</span><br><span class="line"> rawData: { data, series },</span><br><span class="line"> } = <span class="keyword">this</span>.props;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> option = {</span><br><span class="line"> grid: {</span><br><span class="line"> top: <span class="number">24</span>,</span><br><span class="line"> bottom: <span class="number">8</span>,</span><br><span class="line"> left: <span class="number">24</span>,</span><br><span class="line"> right: <span class="number">36</span>,</span><br><span class="line"> containLabel: <span class="literal">true</span>,</span><br><span class="line"> },</span><br><span class="line"> legend: { <span class="attr">data</span>: series.map(<span class="function"><span class="params">item</span> =></span> item.name), selected },</span><br><span class="line"> tooltip: {</span><br><span class="line"> trigger: <span class="string">'axis'</span>,</span><br><span class="line"> formatter(params: any) {</span><br><span class="line"> <span class="keyword">let</span> text = <span class="string">''</span>;</span><br><span class="line"> <span class="keyword">if</span> (_.isArray(params)) {</span><br><span class="line"> text = <span class="string">`<span class="subst">${moment(<span class="built_in">Number</span>(params[<span class="number">0</span>].axisValue)).format(</span></span></span><br><span class="line"><span class="string"><span class="subst"> <span class="string">'YYYY-MM-DD HH:mm:ss'</span>,</span></span></span><br><span class="line"><span class="string"><span class="subst"> )}</span><br/>`</span>;</span><br><span class="line"> _.forEach(params, obj => {</span><br><span class="line"> <span class="keyword">let</span> value = obj.value;</span><br><span class="line"> text = <span class="string">`<span class="subst">${text}</span><span class="subst">${obj.marker}</span><span class="subst">${obj.seriesName}</span>: <span class="subst">${value}</span><br/>`</span>;</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> text = <span class="string">`<span class="subst">${moment(<span class="built_in">Number</span>(params.axisValue)).format(</span></span></span><br><span class="line"><span class="string"><span class="subst"> <span class="string">'YYYY-MM-DD HH:mm:ss'</span>,</span></span></span><br><span class="line"><span class="string"><span class="subst"> )}</span><br/>`</span>;</span><br><span class="line"> text = <span class="string">`<span class="subst">${text}</span><span class="subst">${params}</span><span class="subst">${params.seriesName}</span>: <span class="subst">${params.value}</span>`</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> text;</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> xAxis: {</span><br><span class="line"> data: data.map(<span class="function"><span class="params">o</span> =></span> o.timestamp),</span><br><span class="line"> axisLabel: {</span><br><span class="line"> formatter(value: string) {</span><br><span class="line"> <span class="keyword">return</span> moment(<span class="built_in">Number</span>(value)).format(<span class="string">'HH:mm'</span>);</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> yAxis: {</span><br><span class="line"> type: <span class="string">'value'</span>,</span><br><span class="line"> },</span><br><span class="line"> series: series,</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> option;</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> onLegendSelectChanged = (e: {</span><br><span class="line"> name: string;</span><br><span class="line"> selected: { [k: string]: boolean };</span><br><span class="line"> }) => {</span><br><span class="line"> <span class="keyword">let</span> selected = <span class="built_in">Object</span>.assign({}, e.selected);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.clickedLegendName === e.name) {</span><br><span class="line"> <span class="keyword">this</span>.clickedLegendName = <span class="string">''</span>;</span><br><span class="line"> <span class="built_in">Object</span>.keys(selected).forEach(<span class="function"><span class="params">key</span> =></span> {</span><br><span class="line"> selected[key] = <span class="literal">true</span>;</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">this</span>.clickedLegendName = e.name;</span><br><span class="line"> <span class="built_in">Object</span>.keys(selected).forEach(<span class="function"><span class="params">key</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (key === e.name) {</span><br><span class="line"> selected[key] = <span class="literal">true</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> selected[key] = <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> option = <span class="keyword">this</span>.getEchartsOption(selected);</span><br><span class="line"> <span class="keyword">this</span>.echartRef.setOption(option);</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">const</span> { className, style, loading } = <span class="keyword">this</span>.props;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <Root</span><br><span class="line"> className={className}</span><br><span class="line"> style={style}</span><br><span class="line"> ></span><br><span class="line"> <ReactEcharts</span><br><span class="line"> ref={(ref:any) => (<span class="keyword">this</span>.echartRef = ref && ref.getEchartsInstance())}</span><br><span class="line"> option={<span class="keyword">this</span>.getEchartsOption()}</span><br><span class="line"> showLoading={loading}</span><br><span class="line"> onEvents={{</span><br><span class="line"> legendselectchanged: <span class="keyword">this</span>.onLegendSelectChanged,</span><br><span class="line"> }}</span><br><span class="line"> /></span><br><span class="line"> <<span class="regexp">/Root></span></span><br><span class="line"><span class="regexp"> );</span></span><br><span class="line"><span class="regexp"> }</span></span><br><span class="line"><span class="regexp">}</span></span><br></pre></td></tr></table></figure></p>]]></content>
<categories>
<category> web前端 </category>
</categories>
<tags>
<tag> echarts-for-react </tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/start/hello-world/"/>
<url>/start/hello-world/</url>
<content type="html"><![CDATA[<blockquote><p>Hello World 中文意思是『你好,世界』。因为《The C Programming Language》中使用它做为第一个演示程序,非常著名,所以后来的程序员在学习编程或进行设备调试时延续了这一习惯。 ——百度百科</p></blockquote><p>我自己开始学写代码的时候,写的第一段代码也是Hello World, 所以我把它作为博客的第一篇文章。</p><p>控制台版本<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'Hello World'</span>)</span><br></pre></td></tr></table></figure></p><a id="more"></a><p>React函数式组件版本<br><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">div</span>></span>Hello World<span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>React类组件版本<br><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Demo</span> </span>{</span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">div</span>></span>Hello World<span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<categories>
<category> web前端 </category>
</categories>
<tags>
<tag> hello-world </tag>
</tags>
</entry>
</search>