<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>DappWind</title>
  
  <subtitle>W.IND.SS.ON</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://blog.dappwind.com/"/>
  <updated>2021-12-14T05:50:44.028Z</updated>
  <id>https://blog.dappwind.com/</id>
  
  <author>
    <name>DappWind</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>React Conf 2021 React大会总结</title>
    <link href="https://blog.dappwind.com/2021/12/14/"/>
    <id>https://blog.dappwind.com/2021/12/14/</id>
    <published>2021-12-13T18:54:16.000Z</published>
    <updated>2021-12-14T05:50:44.028Z</updated>
    
    <content type="html"><![CDATA[<p>本次react 大会 我认为技术层的重点在react服务端渐进渲染 和 react native desktop。</p><h2 id="一-Suspense-和-服务端渐进渲染"><a href="#一-Suspense-和-服务端渐进渲染" class="headerlink" title="一. Suspense 和 服务端渐进渲染"></a>一. Suspense 和 服务端渐进渲染</h2><p>Suspense 是解决 <code>加载中</code> 的问题。</p><p>之前这种异步加载的加载中状态是使用state，手动来控制显示隐藏，多个接口请求时，还需要手动提升状态。</p><p>Suspense 封装了这个逻辑，其实这个之前已经可以用了，这次最大的改变是在服务端渲染中的支持，这可以解决之前诟病已久的“服务端渲染白屏”问题。</p><p>这个问题在我们的私募服务中尤其明显，当服务端的接口很慢时，服务端渲染白屏时间有时要1s，打开体验比不用SSR差很多，后续不得不把慢接口请求都放在了web端，进而实现骨架屏和渐进加载。</p><p><img src="https://files.mdnice.com/user/11750/63624c85-9e0c-44fb-8fc3-7c1fead7eb2f.png" alt></p><h3 id="服务端渐进渲染-steam-server-render-with-Suspense"><a href="#服务端渐进渲染-steam-server-render-with-Suspense" class="headerlink" title="服务端渐进渲染  steam server render with Suspense"></a>服务端渐进渲染  steam server render with Suspense</h3><p>特性如下：</p><ul><li>慢的组件不会拖慢 整页的首屏时间</li><li>先展示初始html 再流式渲染剩余页面</li><li>代码分割跟服务端渲染完整融合起来</li></ul><p><img src="https://files.mdnice.com/user/11750/3cce1acc-cd67-4f22-8d08-6c5570a9398d.png" alt></p><p><code>steaming HTML</code></p><p>与之前的妥协做法不同（先骨架屏然后交由web渲染），始终是服务端，并且是逐渐渲染的，HTML加载也是渐进式。接口在服务端请求 自然会比客户端快。</p><p>另外一个特点是 不用所有的组件都渲染完 就能交互。</p><h3 id="show-me-the-code"><a href="#show-me-the-code" class="headerlink" title="show me the code"></a>show me the code</h3><p><strong>具体这个steaming HTML怎么实现的大家可以看下这个介绍</strong></p><blockquote><p><a href="https://github.com/reactwg/react-18/discussions/22" target="_blank" rel="noopener">https://github.com/reactwg/react-18/discussions/22</a></p></blockquote><p><strong>而 demo 则可以使用这个例子测试下, 效果很好</strong></p><blockquote><p><a href="https://codesandbox.io/s/kind-sammet-j56ro" target="_blank" rel="noopener">https://codesandbox.io/s/kind-sammet-j56ro</a></p></blockquote><h3 id="server-components"><a href="#server-components" class="headerlink" title="server components"></a>server components</h3><p>这部分在 Hydrogen + React 18 中介绍。shopify的工程师结合react server components 开发了 Hydrogen 这个框架，实现他们的服务端渐进渲染。介绍了些特性。</p><p>比如首页初始js会非常小，以及一些特性的图示如下</p><p><img src="https://files.mdnice.com/user/11750/f8d36e3f-6e5f-45fc-885f-192724aad36e.png" alt></p><p>demo</p><blockquote><p><a href="https://stackblitz.com/edit/shopify-hydrogen-ynuk8b?title=Hydrogen" target="_blank" rel="noopener">https://stackblitz.com/edit/shopify-hydrogen-ynuk8b?title=Hydrogen</a></p></blockquote><h2 id="二-react-native-desktop"><a href="#二-react-native-desktop" class="headerlink" title="二. react native desktop"></a>二. react native desktop</h2><p>另外一个重头戏就是RN的桌面端了。</p><p>facebook messager 和 微软的 xbox 商店都使用RN来开发的。</p><p>facebook messager之前使用electron开发，列了一些之前不足：</p><ul><li>需要维护electron</li><li>不支持OpenGL 需要转成 WebGL</li><li>Qt开发不便</li></ul><p>使用RN开发的优点：</p><p>RN支持OpenGL 和 原生windowAPI 原生播放器等</p><p><img src="https://files.mdnice.com/user/11750/ee8264fd-9ce5-4d85-bc85-7a88596e7adf.png" alt></p><p>对比electron</p><ul><li>包体积降低 80%</li><li>冷启动时间减低 60%</li><li>app崩溃率降低 50%</li></ul><p><strong>接下来是微软的开发介绍</strong></p><p>微软主要介绍了xbox应用商店使用RN开发，包括 桌面版、XSX XSS的主机版</p><p><img src="https://files.mdnice.com/user/11750/8a1bde12-afef-4fd2-b207-a66659183302.png" alt></p><p>以及选择 js 和 react rn进行开发的原因</p><p><img src="https://files.mdnice.com/user/11750/0579f6ce-bd18-4175-a5d6-77a639529bb4.png" alt></p><p>xbox的 electron版 和 rn版 对比：</p><ul><li>使用原生组件，视频等性能更好</li><li>内存占用从1.6G 降到到 350M</li><li>cpu从50%降低到30%</li></ul><p>react-native-windows 官网</p><blockquote><p><a href="https://microsoft.github.io/react-native-windows/" target="_blank" rel="noopener">https://microsoft.github.io/react-native-windows/</a></p></blockquote><h2 id="三-其他的一些主题："><a href="#三-其他的一些主题：" class="headerlink" title="三. 其他的一些主题："></a>三. 其他的一些主题：</h2><h3 id="keynote"><a href="#keynote" class="headerlink" title="keynote"></a>keynote</h3><p>总领介绍了react本次大会的内容。讲得非常好，推荐大家完整看下。</p><blockquote><p><a href="https://www.youtube.com/watch?v=FZ0cG47msEk&amp;list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&amp;index=1" target="_blank" rel="noopener">https://www.youtube.com/watch?v=FZ0cG47msEk&amp;list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&amp;index=1</a></p></blockquote><h3 id="react-docs"><a href="#react-docs" class="headerlink" title="react docs"></a>react docs</h3><p>这部分介绍了react的可交互新文档</p><p>文档里多了 codesandbox 和 expo 可交互的例子。</p><p>不管是专家还是新手，都值得通看一遍。</p><p>react</p><blockquote><p><a href="https://beta.reactjs.org/" target="_blank" rel="noopener">https://beta.reactjs.org/</a></p></blockquote><p>react native</p><blockquote><p><a href="https://reactnative.dev/" target="_blank" rel="noopener">https://reactnative.dev/</a></p></blockquote><h3 id="React-Devtools-调试技巧"><a href="#React-Devtools-调试技巧" class="headerlink" title="React Devtools 调试技巧"></a>React Devtools 调试技巧</h3><blockquote><p><a href="https://www.youtube.com/watch?v=oxDfrke8rZg&amp;list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&amp;index=5" target="_blank" rel="noopener">https://www.youtube.com/watch?v=oxDfrke8rZg&amp;list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&amp;index=5</a></p></blockquote><h3 id="react-18-for-app-developers"><a href="#react-18-for-app-developers" class="headerlink" title="react 18 for app developers"></a>react 18 for app developers</h3><p>介绍了如何升级到react 18, 以及一些特性的简介。</p><p>服务端 Suspense</p><p>react memo tab点击效果 需要立即响应时，可以用这个来提高速度，在活动页的tab渲染中有过这个问题</p><p>cpu slowdown</p><h3 id="react-without-memo"><a href="#react-without-memo" class="headerlink" title="react without memo"></a>react without memo</h3><p>yarn forget<br>程序会自动化帮着加一些 memo，来提升性能。</p><h3 id="On-device-Machine-Learning-for-React-Native"><a href="#On-device-Machine-Learning-for-React-Native" class="headerlink" title="On-device Machine Learning for React Native"></a>On-device Machine Learning for React Native</h3><p>介绍了RN如何使用机器学习<br><code>react-native-pytorch-core</code></p><h3 id="React-18-for-External-Store-Libraries"><a href="#React-18-for-External-Store-Libraries" class="headerlink" title="React 18 for External Store Libraries"></a>React 18 for External Store Libraries</h3><p>介绍了如何在三方库使用</p><h3 id="The-ROI-of-Designing-with-React"><a href="#The-ROI-of-Designing-with-React" class="headerlink" title="The ROI of Designing with React"></a>The ROI of Designing with React</h3><p>给设计师介绍react</p>]]></content>
    
    <summary type="html">
    
      本次react 大会 我认为技术层的重点在react服务端渐进渲染 和 react native desktop。

一. Suspense 和 服务端渐进渲染
Suspense 是解决 加载中 的问题。

之前这种异步加载的加载中状态是使用state，手动来控制显示隐藏，多个接口请求时，还需要手动提升状态。

Suspense 封装了这个逻辑，其实这个之前已经可以用了，这次最大的改变是在服务端渲
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>机器学习入门</title>
    <link href="https://blog.dappwind.com/2020/09/02/"/>
    <id>https://blog.dappwind.com/2020/09/02/</id>
    <published>2020-09-01T16:35:14.000Z</published>
    <updated>2021-05-08T05:57:24.153Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/31hy7.png" alt></p><blockquote><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/e36du.png" alt><br>我们总有一种感觉，机器学习门槛高、难入门。这是因为这里有太多晦涩的概念「神经网络」、「评估指标」、「优化算法」等让初学者老是有种盲人摸象的感觉。甚至连理解一个 Tensorflow 官方 Demo 都感觉吃力，因此不少开发者就有过「机器学习从入门到放弃」的经历。<br>​本文站在全局视角，通过分析一个 TensorFlow 官方的 Demo 来达到俯瞰一个「机器学习」系统的效果，从而让读者看清这个头大象的全貌，帮助初学者入门「机器学习」。</p></blockquote><h2 id="如何理解机器学习系统"><a href="#如何理解机器学习系统" class="headerlink" title="如何理解机器学习系统"></a>如何理解机器学习系统</h2><p>「机器学习」的目标就是利用已有答案来寻找规则，从而做出预测。<br>这与「传统系统」的区别在于：</p><ul><li>「传统系统」的目标是获得答案</li><li>「机器学习」的目标是利用已有答案获得规则</li></ul><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/xvlb9.png" alt></p><h2 id="详解一个机器学习-Demo"><a href="#详解一个机器学习-Demo" class="headerlink" title="详解一个机器学习 Demo"></a>详解一个机器学习 Demo</h2><p>学习一项技能最好方法就是去使用它。这部分我们来看一个 TensorFlow Demo。TensorFlow 是 Google 推出的深度学习框架，基本信息我就不多做介绍了。</p><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/yos4d.png" alt></p><p>看到这部分代码的全貌，什么感觉？<br><em>我第一次读到的感觉是：「语法都能看懂，但就是不知道你这是要干啥！」</em></p><p>这个 Demo 实际上是要训练一个可以识别手写数字的模型（Model）, 要识别的手写数字长这样：</p><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/q96fp.png" alt></p><p>你也许一下子会有很多问号。手写数字？ 图片在哪？怎么识别？<br>别急，下面我来为大家详解这个 Demo。</p><h3 id="数据准备"><a href="#数据准备" class="headerlink" title="数据准备"></a>数据准备</h3><p>人工智能领域中的数据是什么？我们从 TensorFlow 这个框架的名字中就能看出来 –  Tensor（张量）形成的 Flow（流）。<br>在「人工智能」领域，绝大部分数据都是以 Tensor 的形式存在，而 Tensor 可以直接理解成多维数组。</p><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/ptrk7.png" alt></p><p><strong>举个例子:  要把一张图片输入到人工智能模型中。</strong><br>我们第一反应是要先把图片数字化，用 Base64 来表示这张图、或者用二进制等等。但是对于人工智能系统，最佳方式是把图片转换成 Tensor。<br>我们试试用 Tensor 来表示一张 <strong>像素 3*3 、背景为白色、对角线为黑色的图片：</strong></p><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/a7eo6.png" alt><br><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/yracx.png" alt></p><p>运行代码之后，我们就得到了那张对角线是黑色的 3<em>3 图片。<br>这就是<strong>用一个四阶 Tensor 表示一张图片</strong>，Tensor 形状为 <code>(1, 3, 3)</code> 。<br>同理如果要表示 6000 张 28</em>28 的图片，那么 Tensor 的形状就是  <code>(6000, 28, 28)</code>。</p><p>现在我们阅读第一部分的代码：<br><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/z9r51.png" alt></p><p><strong>「MNIST」</strong>(Mixed National Institute of Standards and Technology database) 是美国国家标准与技术研究院收集整理的大型手写数字数据库，包含 60,000 个示例的训练集以及 10,000 个示例的测试集，里面的图片长这样。</p><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/hwrmw.png" alt></p><p>这些图片都是通过空间的矩阵的方式存储的：</p><p><img src="image/2.png" alt></p><p>这样我们就明白这段代码的意思了，是从 mnist 中获取用于训练的的数据集集（ x_trian，y_train ），以及用于测试的数据集（ x_test，y_test ）。</p><ul><li>x_trian 形状为 (6000, 28, 28) ，表示 6000 张 28*28的图片。</li><li>_trian 形状为 (6000,)，表示 x_train 对应的数字答案。</li></ul><h2 id="模型（model）是什么"><a href="#模型（model）是什么" class="headerlink" title="模型（model）是什么"></a>模型（model）是什么</h2><p>得到了数据集之后，是不是可以开始训模型了？别急，我们要搞清楚模型是什么，Tensorflow 文档是这样定义模型:</p><blockquote><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/e36du.png" alt><br>在机器学习中，模型（ Model ）是一个具有可学习参数的<strong>函数</strong>，它将输入映射到输出。<strong>最优参数</strong>是通过在数据上训练模型获得的。一个训练有素的模型将提供从输入到所需输出的精确映射。</p></blockquote><p><strong>我来帮你们翻译一下这个定义：模型是个函数，这里面内置了很多参数，这些参数的值会直接影响模型的输出结果。有意思的是这些参数都是可学习的，它们可以根据训练数据的来进行调整来达到一组最优值，使得模型的输出效果最理想。</strong></p><ul><li>那么模型里参数又是什么？</li><li>Demo 当中模型传入的 4 个Layer 又是什么含义？</li></ul><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/uspqt.png" alt></p><ul><li>模型又是如何训练的？</li></ul><h2 id="神经网络-（-Neural-Network-）"><a href="#神经网络-（-Neural-Network-）" class="headerlink" title="神经网络 （ Neural Network ）"></a>神经网络 （ Neural Network ）</h2><p>神经网络 （ Neural Network ）顾名思义，就是用神经元 （ Neuron ）连接而成的网络（ Network ）。<br><strong>那么什么是神经元？</strong></p><blockquote><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/e36du.png" alt><br>机器学习中的神经元（ Neuron ） 源于生物神经网络 – 通过电位变化表示“兴奋”的生物神经元。<br>在机器学习领域，一个神经元其实是一个计算单元。它需要被输入N 个信号后开始计算（兴奋），这些信号通过<strong>带权重（weights）的连接</strong>传递给了神经元，神经元通过加权求和，计算出一个值。然后这个值会通过激活函数（ activation function ）的处理，产生输出，通常是被压缩在 0~1 之间的数字。</p></blockquote><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/9wwdx.png" alt></p><p>Demo 当中，第一个 Layer 就是把就是把 28*28 的图片展开成一个包含 784 个神经元一维数组。<br><figure class="highlight py"><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><br><span class="line"><span class="comment"># 第一个 Layer</span></span><br><span class="line"><span class="comment"># 神经元展开成一维数组</span></span><br><span class="line">tf.keras.layers</span><br><span class="line">.Flatten(input_shape=(<span class="number">28</span>, <span class="number">28</span>)),</span><br><span class="line">...</span><br></pre></td></tr></table></figure></p><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/imi1k.png" alt></p><p><strong>第二个 Layer：</strong><br><figure class="highlight py"><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></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">tf.keras.layers</span><br><span class="line">.Dense(<span class="number">128</span>, activation=<span class="string">'relu'</span>),</span><br><span class="line">...</span><br></pre></td></tr></table></figure></p><p>Layer2 传入了参数 <code>activation=&#39;relu&#39;</code>，意思是用 relu 作为激活函数 。<br>我们先来理解下什么是「激活函数」，</p><blockquote><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/e36du.png" alt><br>当我们的大脑同时接收到大量信息时，它会努力理解并将信息分为 “有用 “和 “不那么有用 “的信息。在神经网络的情况下，我们需要一个类似的机制来将输入的信息分为 「有用 」或 “「不太有用」。<br>这对机器学习很重要，因为不是所有的信息都是同样有用的，有些信息只是噪音。这就是激活函数的作用，激活函数帮助网络使用重要的信息，抑制不相关的数据点。<br>例如 Demo 中，Layer1 输出 784 个神经元，并不是全部激活的。而只有激活神经元才能对 Layer2 产生刺激，而  layer4 输出10个神经元，其中第 2 个神经元激活，表示识别结果为 1 的概率是 99%。</p></blockquote><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/yn25n.png" alt></p><p>所以 relu 是激活函数的一种，用于神经元的激活 – 根据上一个 Layer 给予的刺激算出神经元最后输出（显示）的那个数字。<br>Layer2 层有 128个神经元，这128个神经元会和 Layer1 中 728 个神经元相互连接，共将产生 <code>728 * 128 =93184</code> 权重（weights）各自不同的连接 。Layer1 中神经元的输出将与连接到 layer2 的权重值进行加权求和，得到的结果会被带入 <code>relu</code> 函数，最终输出一个新的值作为 Layer2 中神经元的输出。</p><p><img src="./image/relu.png" alt></p><p><strong>第三个 Layer</strong><br><figure class="highlight py"><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><br><span class="line">  tf.keras.layers.Dropout(<span class="number">0.2</span>),</span><br></pre></td></tr></table></figure></p><p>Dropout layer 的主要作用就是防止过度拟合。过渡拟合现象主要表现是：<strong>最终模型在训练集上效果好；在测试集上效果差。模型泛化能力弱。</strong><br>Dropout 解决过度拟合的办法之一，就是随机丢弃一部神经元。Demo 中就是使用  Dropout 随机丢弃 20% 神经元。</p><p><img src="./image/dropout.ong" alt></p><p><strong>第四个 Layer</strong></p><figure class="highlight py"><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></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">tf.keras.layers</span><br><span class="line">.Dense(<span class="number">10</span>, activation=<span class="string">'softmax'</span>)</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p> Layer4 上有 10 个神经元，并使用 <code>softmax</code>作为激活函数，这 10个神经元的输出就是最终结的结果。下图为识别一个手写数字 1 的整个过程，各层神经元逐层激活，最终输出预测结果。<br><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/bstil.gif" alt></p><p>到这里，我们通过了解 4 个Layer之间的作用关系简单的了解了一个神经网络的运作方式。</p><h2 id="模型训练补充"><a href="#模型训练补充" class="headerlink" title="模型训练补充"></a>模型训练补充</h2><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/mxrtj.png" alt></p><p>要读懂这段代码，我们要先通过一个类比来理解下什么是: <strong>损失函数（ Loss Function ）</strong>、<strong>优化算法（ Optimization Algorithms ）</strong>、<strong>评价指标（ Evaluation Metrics ）</strong><br>假如一名男士要开始锻炼身体，目标是胸围达到 120cm，且身材看起来匀称（别太壮）：</p><ul><li>经过反复训练，他的胸围达到了 110cm，那么我们可以把<code>Loss = |目标（120cm）- 当前（110cm）|</code>作为一个最简单的<strong>损失函数（Loss Function）。</strong>而 Demo 中的 Loss Function 用的是 - 稀疏类别交叉熵（sparse_categorical_crossentropy），这个算法的特点就是擅长分类。</li><li>是否达成目标，不能仅仅使用损失函数来判断。身材匀称、美观也很重要，而评价指标（Evaluation Metrics ）的作用就给我们提供了一个评判标准。</li><li>接下来我们就要寻找产生 Loss 的规律，Loss 不仅仅是胸围小于 130cm 的损失，胸围大于 130cm 而导致美感损失也是 Loss 的一部分。因此想达到最佳效果，既不能练的太轻也不能练的太用力。我们给予训练要素不同的权重（ Weights ），蛋白质补充权重为w0、胸肌上沿训练强度w1、胸肌中部训练强度w2、胸肌下沿训练强度w3、有氧运动训练强度w4 ，等等的影响因素我们都加上不同的权重。最后得到一组 [w1, w2…wn] 。而通过不断调整[w1, w2…wn] 得出最佳胸肌锻炼的方法，就是优化算法（ Optimization Algorithms ）。<br>了神经网络的模型、层、权重、优化算法、损失函数以及评估指标等之后，我们就可以读懂 Demo 中那段代码了。现在尝试画一张神经网络的工作流程图，串一串一个神经网络的工作流程。</li></ul><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/apot8.png" alt></p><h2 id="训练与测试"><a href="#训练与测试" class="headerlink" title="训练与测试"></a>训练与测试</h2><p><img src="https://chendongze.oss-cn-shanghai.aliyuncs.com/ipic/tw8yy.png" alt></p><p>这部分很好理解，带入数据训练、测试就好。<br>说一下 <code>epochs</code> 。<br>在神经网络领域，一个 epoch 是指整个训练数据集的训练一个周期。<br><strong>1 epoch = 1正向传播（ forward pass ）+ 1 反向传播（ backward pass ）*</strong>（我们可以简单的理解，正向传播目的是为了获得预测结果，反向传播目的是调整到最优的权重（weights），来让 Loss 最小化。）* </p><p>Demo 中 epochs = 5  是因为 1次 epoch 很可能得不到最优的权重（weights）。 既然 1 次不能满足，那就 5 次，5 次还不满足就 10 次，直到效果最小化 Loss 的效果不再变化。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>如果认真阅读了本文，那么我相信你已经对人工智能已经有了一点整体的认识，本文给了你一个鸟瞰人工智能的视角，摆脱了盲人摸象的感觉。这虽然不是魔法，能立刻把你变成人工智能大神，但对基本架构的进一步理解会增强你对人工智能的自学能力。无论你是从事前端、后端、全栈等技术开发者，或者只是对人工智能感兴趣，我都希望本文可以带给你一个新的视角去理解人工智能，让你读有所思，思有所得，得有所想，想有所获，或有所益。</p><blockquote><p>原文链接 <a href="https://github.com/netpi/explain-a-tensorflow-demo" target="_blank" rel="noopener">https://github.com/netpi/explain-a-tensorflow-demo</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      我们总有一种感觉，机器学习门槛高、难入门。这是因为这里有太多晦涩的概念「神经网络」、「评估指标」、「优化算法」等让初学者老是有种盲人摸象的感觉。甚至连理解一个 Tensorflow 官方 Demo 都感觉吃力，因此不少开发者就有过「机器学习从入门到放弃」的经历。
​本文站在全局视角，通过分析一个 TensorFlow 官方的 Demo 来达到俯瞰一个「机器学习」系统的效果，从而让读者看清这个头大象
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>机器学习生成CRM表单 - 5.Tenserflow服务部署</title>
    <link href="https://blog.dappwind.com/2020/06/14/"/>
    <id>https://blog.dappwind.com/2020/06/14/</id>
    <published>2020-06-13T16:32:24.000Z</published>
    <updated>2020-07-21T15:20:29.160Z</updated>
    
    <content type="html"><![CDATA[<p>在colab上开发测试完成后，投入到生产中，就要部署为服务了。采用通用restful api的形式，通过接口上传图片，json格式返回识别出的数据。</p><h2 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h2><h2 id="服务框架"><a href="#服务框架" class="headerlink" title="服务框架"></a>服务框架</h2><p>由于tf为python语言，所以直接用了python的轻量级框架flask，这里只提供一个接口，就是分析图片内CRM组件的接口。</p><figure class="highlight python"><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="comment"># -*- coding: utf-8 -*</span></span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, jsonify, request</span><br><span class="line"><span class="keyword">from</span> service.tf <span class="keyword">import</span> tfServer</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">ALLOWED_EXTENSIONS = set([<span class="string">'png'</span>, <span class="string">'PNG'</span>, <span class="string">'jpg'</span>, <span class="string">'jpeg'</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route('/')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">root</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">'D2C后台服务'</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">allowed_file</span><span class="params">(filename)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">'.'</span> <span class="keyword">in</span> filename <span class="keyword">and</span> \</span><br><span class="line">           filename.rsplit(<span class="string">'.'</span>, <span class="number">1</span>)[<span class="number">1</span>] <span class="keyword">in</span> ALLOWED_EXTENSIONS</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route('/generate/form', methods=['POST'])</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">generate_form</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">if</span> request.files:</span><br><span class="line">        file = request.files[<span class="string">'file'</span>]</span><br><span class="line">        <span class="keyword">if</span> file <span class="keyword">and</span> allowed_file(file.filename):</span><br><span class="line">            file.save(<span class="string">'images/'</span>+file.filename)</span><br><span class="line">            result = tfServer(<span class="string">'images/'</span>+file.filename)</span><br><span class="line">            <span class="keyword">return</span> jsonify(&#123;<span class="string">'success'</span>: <span class="string">'true'</span>, <span class="string">'data'</span>: result&#125;)</span><br><span class="line">    <span class="keyword">return</span> jsonify(&#123;<span class="string">'success'</span>: <span class="string">'false'</span>, <span class="string">'message'</span>: <span class="string">'请传文件-file'</span>&#125;)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    <span class="comment"># app.debug = True</span></span><br><span class="line">    app.run(host=<span class="string">"0.0.0.0"</span>, port=<span class="number">5000</span>)</span><br></pre></td></tr></table></figure><p>flask部分较为简单，服务启动在 0.0.0.0:5000 ，这里需设置host 0.0.0.0否则外网无法访问。</p><p>提供一个api接口 <code>/generate/form</code> 方法为<code>POST</code>, flask对于文件上传处理也比较方便，直接 request.files 就可以获取到文件名等信息。file.save 就可以报错到文件目录。对比 node koa 需要中间件 koa-body 处理文件上传，然后fs.createReadStream读取文件流，fs.createWriteStream 写到目标文件中。</p><p>allowed_file 函数用于对上传文件类型的简单判断，只允许几个图片的后缀。</p><p>随后就是调用colab上测试好的识别方法了，最后输出时通过 jsonify 包装一下。</p><h2 id="图像识别服务"><a href="#图像识别服务" class="headerlink" title="图像识别服务"></a>图像识别服务</h2><p>上述代码中的 <code>tfServer(&#39;images/&#39;+file.filename)</code> 就是调用的<code>service</code>文件夹下的<code>tfServer</code>方法。</p>]]></content>
    
    <summary type="html">
    
      在colab上开发测试完成后，投入到生产中，就要部署为服务了。采用通用restful api的形式，通过接口上传图片，json格式返回识别出的数据。

部署方式
服务框架
由于tf为python语言，所以直接用了python的轻量级框架flask，这里只提供一个接口，就是分析图片内CRM组件的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>机器学习生成CRM表单 - 4.生成前端代码</title>
    <link href="https://blog.dappwind.com/2020/05/18/"/>
    <id>https://blog.dappwind.com/2020/05/18/</id>
    <published>2020-05-18T12:02:53.000Z</published>
    <updated>2020-07-19T16:53:57.206Z</updated>
    
    <content type="html"><![CDATA[<p>上文我们已经识别出了组件，组件文案。接下来就可以通过这些数据生成前端代码了。</p><p>这里沿用阿里DSL的格式，先将识别内容生成通用scheme,再把scheme根据不同的DSL转换为相应语言、框架的代码。这样做一方面提高通用性，另一方面，可以把scheme复制到编辑器中进行可视化编辑。</p><p>编辑器demo:</p><blockquote><p><a href="https://blog.dappwind.com/crm-editor/#/zh-CN/">https://blog.dappwind.com/crm-editor/#/zh-CN/</a></p></blockquote><p>以这个图片为例<br><img src="https://g.dappwind.com/static/20200515215044.png" alt></p><h2 id="表单项-自动生成-英文驼峰命名"><a href="#表单项-自动生成-英文驼峰命名" class="headerlink" title="表单项 自动生成 英文驼峰命名"></a>表单项 自动生成 英文驼峰命名</h2><p>根据元素前面的文字，调用有道翻译的api，翻译出英语，然后再转为驼峰法命名。</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 翻译 获取英文驼峰命名</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> re</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">def</span> <span class="title">str2Hump</span><span class="params">(text)</span>:</span></span><br><span class="line">    arr = filter(<span class="literal">None</span>, text.lower().split(<span class="string">' '</span>))</span><br><span class="line">    res = <span class="string">''</span></span><br><span class="line">    j = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> arr:</span><br><span class="line">        <span class="keyword">if</span> j == <span class="number">0</span>:</span><br><span class="line">            res = i</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            res = res + i[<span class="number">0</span>].upper() + i[<span class="number">1</span>:]</span><br><span class="line">        j += <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> res</span><br><span class="line"> </span><br><span class="line"><span class="comment"># 翻译函数，word 需要翻译的内容</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">translate</span><span class="params">(word)</span>:</span></span><br><span class="line">    word = re.sub(<span class="string">r'[^\w\s]'</span>,<span class="string">''</span>, word <span class="keyword">or</span> <span class="string">'默认值'</span>)</span><br><span class="line">    <span class="comment"># 有道词典 api</span></span><br><span class="line">    url = <span class="string">'http://fanyi.youdao.com/translate?smartresult=dict&amp;smartresult=rule&amp;smartresult=ugc&amp;sessionFrom=null'</span></span><br><span class="line">    <span class="comment"># 传输的参数，其中 i 为需要翻译的内容</span></span><br><span class="line">    key = &#123;</span><br><span class="line">        <span class="string">'type'</span>: <span class="string">"AUTO"</span>,</span><br><span class="line">        <span class="string">'i'</span>: word,</span><br><span class="line">        <span class="string">"doctype"</span>: <span class="string">"json"</span>,</span><br><span class="line">        <span class="string">"version"</span>: <span class="string">"2.1"</span>,</span><br><span class="line">        <span class="string">"keyfrom"</span>: <span class="string">"fanyi.web"</span>,</span><br><span class="line">        <span class="string">"ue"</span>: <span class="string">"UTF-8"</span>,</span><br><span class="line">        <span class="string">"action"</span>: <span class="string">"FY_BY_CLICKBUTTON"</span>,</span><br><span class="line">        <span class="string">"typoResult"</span>: <span class="string">"true"</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment"># key 这个字典为发送给有道词典服务器的内容</span></span><br><span class="line">    response = requests.post(url, data=key)</span><br><span class="line">    <span class="comment"># 判断服务器是否相应成功</span></span><br><span class="line">    <span class="keyword">if</span> response.status_code == <span class="number">200</span>:</span><br><span class="line">        <span class="comment"># 然后相应的结果</span></span><br><span class="line">        result = json.loads(response.text)</span><br><span class="line">        text = result[<span class="string">'translateResult'</span>][<span class="number">0</span>][<span class="number">0</span>][<span class="string">'tgt'</span>]</span><br><span class="line">        <span class="keyword">return</span> str2Hump(text)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        print(<span class="string">"有道词典调用失败"</span>)</span><br><span class="line">        <span class="comment"># 相应失败就返回空</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br></pre></td></tr></table></figure><p>结果打印如下</p><figure class="highlight python"><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">表单项： 活动名称 ———— 驼峰命名：theNameOfTheEvent</span><br><span class="line">表单项：位置———— 驼峰命名：location</span><br><span class="line">表单项：可配置标题———— 驼峰命名：configurableTitle</span><br><span class="line">表单项： 图片链接 ———— 驼峰命名：imageLinks</span><br><span class="line">表单项：图片跳转链接———— 驼峰命名：picturesJumpLinks</span><br><span class="line">表单项： 基金代码 ———— 驼峰命名：theFundCode</span><br></pre></td></tr></table></figure><h2 id="python数据导出"><a href="#python数据导出" class="headerlink" title="python数据导出"></a>python数据导出</h2><figure class="highlight python"><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="comment"># 将预测结果传入之前的数组</span></span><br><span class="line"><span class="keyword">for</span> index <span class="keyword">in</span> range(len(boxes_batch)):</span><br><span class="line">    boxes_batch[index][<span class="string">'predict'</span>] = predicted_name[index]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 按照纵坐标排序，排序前是 nparray, 排序后已经是list了</span></span><br><span class="line">boxes_batch = sorted(boxes_batch, key= <span class="keyword">lambda</span> item: item[<span class="string">'position'</span>][<span class="number">0</span>][<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line">print(boxes_batch)</span><br><span class="line"><span class="keyword">return</span> boxes_batch</span><br></pre></td></tr></table></figure><p>结果<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[[[[663,170],[200,170],[200,123],[663,123]],&quot;请输入活动名称&quot;,&quot;* 活动名称 :&quot;,&quot;Input&quot;],[[[197,497],[197,449],[666,449],[666,497]],&quot;必填&quot;,&quot;*图片跳转链接&quot;,&quot;Input&quot;],[[[201,79],[201,31],[660,31],[660,79]],&quot;请输入基金代码&quot;,&quot;* 基金代码 :&quot;,&quot;Input&quot;],[[[197,427],[197,380],[665,380],[665,427]],&quot;非必填，不配置不展示&quot;,&quot;可配置标题&quot;,&quot;Input&quot;],[[[197,358],[197,307],[666,307],[666,358]],&quot;&quot;,&quot;位置&quot;,&quot;Select&quot;],[[[663,266],[200,266],[200,219],[663,219]],&quot;请输入图片链接&quot;,&quot;* 图片链接 :&quot;,&quot;Input&quot;]]</span><br></pre></td></tr></table></figure></p><p><img src="https://g.dappwind.com/static/20200518204336.png" alt></p><p>每个数组代表一个组件，里面有组件的位置，文案等</p><p>后续都放在js中处理，方便些。</p><h2 id="生成schema"><a href="#生成schema" class="headerlink" title="生成schema"></a>生成schema</h2><p>按照schema格式生成</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> data = <span class="built_in">require</span>(<span class="string">'./colab_data'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> scheme = &#123;</span><br><span class="line">  <span class="string">"componentName"</span>: <span class="string">"Page"</span>,</span><br><span class="line">  <span class="string">"props"</span>: &#123;&#125;,</span><br><span class="line">  <span class="string">"children"</span>: [&#123;</span><br><span class="line">    <span class="string">"componentName"</span>: <span class="string">"Form"</span>,</span><br><span class="line">    <span class="string">"props"</span>: &#123;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">"children"</span>: [],  </span><br><span class="line">  &#125;],</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data.map(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">  scheme.children[<span class="number">0</span>].children.push(&#123;</span><br><span class="line">    <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">    <span class="string">"props"</span>: &#123;</span><br><span class="line">      <span class="string">"label"</span>: item[<span class="number">2</span>],</span><br><span class="line">      <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">      <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">"children"</span>: [&#123;</span><br><span class="line">      <span class="string">"componentName"</span>: item[<span class="number">3</span>],</span><br><span class="line">      <span class="string">"props"</span>: &#123;</span><br><span class="line">        <span class="string">"placeholder"</span>: item[<span class="number">1</span>],</span><br><span class="line">        <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">fs.writeFileSync(path.join(__dirname,<span class="string">'../test/data_out.js'</span>), <span class="string">`module.exports = <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(scheme)&#125;</span>`</span>);</span><br></pre></td></tr></table></figure><p>生成的scheme如下<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><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"componentName"</span>: <span class="string">"Page"</span>,</span><br><span class="line">  <span class="string">"props"</span>: &#123;&#125;,</span><br><span class="line">  <span class="string">"children"</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">"componentName"</span>: <span class="string">"Form"</span>,</span><br><span class="line">      <span class="string">"props"</span>: &#123;&#125;,</span><br><span class="line">      <span class="string">"children"</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">          <span class="string">"props"</span>: &#123;</span><br><span class="line">            <span class="string">"label"</span>: <span class="string">"* 基金代码 :"</span>,</span><br><span class="line">            <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">            <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"children"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">"componentName"</span>: <span class="string">"Input"</span>,</span><br><span class="line">              <span class="string">"props"</span>: &#123;</span><br><span class="line">                <span class="string">"placeholder"</span>: <span class="string">"请输入基金代码"</span>,</span><br><span class="line">                <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">          <span class="string">"props"</span>: &#123;</span><br><span class="line">            <span class="string">"label"</span>: <span class="string">"* 活动名称 :"</span>,</span><br><span class="line">            <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">            <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"children"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">"componentName"</span>: <span class="string">"Input"</span>,</span><br><span class="line">              <span class="string">"props"</span>: &#123;</span><br><span class="line">                <span class="string">"placeholder"</span>: <span class="string">"请输入活动名称"</span>,</span><br><span class="line">                <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">          <span class="string">"props"</span>: &#123;</span><br><span class="line">            <span class="string">"label"</span>: <span class="string">"* 图片链接 :"</span>,</span><br><span class="line">            <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">            <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"children"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">"componentName"</span>: <span class="string">"Input"</span>,</span><br><span class="line">              <span class="string">"props"</span>: &#123;</span><br><span class="line">                <span class="string">"placeholder"</span>: <span class="string">"请输入图片链接"</span>,</span><br><span class="line">                <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">          <span class="string">"props"</span>: &#123;</span><br><span class="line">            <span class="string">"label"</span>: <span class="string">"位置"</span>,</span><br><span class="line">            <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">            <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"children"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">"componentName"</span>: <span class="string">"Select"</span>,</span><br><span class="line">              <span class="string">"props"</span>: &#123;</span><br><span class="line">                <span class="string">"placeholder"</span>: <span class="string">""</span>,</span><br><span class="line">                <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">          <span class="string">"props"</span>: &#123;</span><br><span class="line">            <span class="string">"label"</span>: <span class="string">"可配置标题"</span>,</span><br><span class="line">            <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">            <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"children"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">"componentName"</span>: <span class="string">"Input"</span>,</span><br><span class="line">              <span class="string">"props"</span>: &#123;</span><br><span class="line">                <span class="string">"placeholder"</span>: <span class="string">"非必填，不配置不展示"</span>,</span><br><span class="line">                <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">"componentName"</span>: <span class="string">"FormItem"</span>,</span><br><span class="line">          <span class="string">"props"</span>: &#123;</span><br><span class="line">            <span class="string">"label"</span>: <span class="string">"*图片跳转链接"</span>,</span><br><span class="line">            <span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line">            <span class="string">"style"</span>: &#123;&#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"children"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">"componentName"</span>: <span class="string">"Input"</span>,</span><br><span class="line">              <span class="string">"props"</span>: &#123;</span><br><span class="line">                <span class="string">"placeholder"</span>: <span class="string">"必填"</span>,</span><br><span class="line">                <span class="string">"hasBorder"</span>: <span class="literal">true</span></span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;</span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>随后调用scheme转前端代码的DSL，我们使用的是<code>antd</code>, 目前还没有好用的DSL，所以在Fusion Design上二次开发，最后生成代码如下</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><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">'use strict'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Input &#125; <span class="keyword">from</span> <span class="string">'antd'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Form &#125; <span class="keyword">from</span> <span class="string">'antd'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Select &#125; <span class="keyword">from</span> <span class="string">'antd'</span>;</span><br><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">'./style.js'</span>;</span><br><span class="line"><span class="keyword">const</span> print = <span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(value);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> FormItem = Form.Item;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Page_0</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">const</span> formItemLayout = &#123;</span><br><span class="line">      labelCol: &#123;</span><br><span class="line">        xs: &#123; <span class="attr">span</span>: <span class="number">24</span> &#125;,</span><br><span class="line">        sm: &#123; <span class="attr">span</span>: <span class="number">4</span> &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      wrapperCol: &#123;</span><br><span class="line">        xs: &#123; <span class="attr">span</span>: <span class="number">24</span> &#125;,</span><br><span class="line">        sm: &#123; <span class="attr">span</span>: <span class="number">16</span> &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        &lt;Form&gt;</span><br><span class="line">          &lt;FormItem label=&#123;<span class="string">'* 基金代码 :'</span>&#125; name=&#123;<span class="string">''</span>&#125; &#123;...formItemLayout&#125;&gt;</span><br><span class="line">            &lt;Input placeholder=&#123;<span class="string">'请输入基金代码'</span>&#125; hasBorder=&#123;<span class="literal">true</span>&#125; /&gt;</span><br><span class="line">          &lt;<span class="regexp">/FormItem&gt;</span></span><br><span class="line"><span class="regexp">          &lt;FormItem label=&#123;'* 活动名称 :'&#125; name=&#123;''&#125; &#123;...formItemLayout&#125;&gt;</span></span><br><span class="line"><span class="regexp">            &lt;Input placeholder=&#123;'请输入活动名称'&#125; hasBorder=&#123;true&#125; /</span>&gt;</span><br><span class="line">          &lt;<span class="regexp">/FormItem&gt;</span></span><br><span class="line"><span class="regexp">          &lt;FormItem label=&#123;'* 图片链接 :'&#125; name=&#123;''&#125; &#123;...formItemLayout&#125;&gt;</span></span><br><span class="line"><span class="regexp">            &lt;Input placeholder=&#123;'请输入图片链接'&#125; hasBorder=&#123;true&#125; /</span>&gt;</span><br><span class="line">          &lt;<span class="regexp">/FormItem&gt;</span></span><br><span class="line"><span class="regexp">          &lt;FormItem label=&#123;'位置'&#125; name=&#123;''&#125; &#123;...formItemLayout&#125;&gt;</span></span><br><span class="line"><span class="regexp">            &lt;Select placeholder=&#123;''&#125; hasBorder=&#123;true&#125; /</span>&gt;</span><br><span class="line">          &lt;<span class="regexp">/FormItem&gt;</span></span><br><span class="line"><span class="regexp">          &lt;FormItem label=&#123;'可配置标题'&#125; name=&#123;''&#125; &#123;...formItemLayout&#125;&gt;</span></span><br><span class="line"><span class="regexp">            &lt;Input placeholder=&#123;'非必填，不配置不展示'&#125; hasBorder=&#123;true&#125; /</span>&gt;</span><br><span class="line">          &lt;<span class="regexp">/FormItem&gt;</span></span><br><span class="line"><span class="regexp">          &lt;FormItem label=&#123;'*图片跳转链接'&#125; name=&#123;''&#125; &#123;...formItemLayout&#125;&gt;</span></span><br><span class="line"><span class="regexp">            &lt;Input placeholder=&#123;'必填'&#125; hasBorder=&#123;true&#125; /</span>&gt;</span><br><span class="line">          &lt;<span class="regexp">/FormItem&gt;</span></span><br><span class="line"><span class="regexp">        &lt;/</span>Form&gt;</span><br><span class="line">      &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp">export default Page_0;</span></span><br></pre></td></tr></table></figure><p>原始原型图如下<br><img src="https://g.dappwind.com/static/20200515215044.png" alt><br>智能生成页面渲染如下<br><img src="https://g.dappwind.com/static/20200518204601.png" alt></p>]]></content>
    
    <summary type="html">
    
      上文我们已经识别出了组件，组件文案。接下来就可以通过这些数据生成前端代码了。

这里沿用阿里DSL的格式，先将识别内容生成通用scheme,再把scheme根据不同的DSL转换为相应语言、框架的代码。这样做一方面提高通用性，另一方面，可以把scheme复制到编辑器中进行可视化编辑。

编辑器demo:

https://blog.dappwind.com/crm-editor/#/zh-CN/


    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>机器学习生成CRM表单 - 3.文字识别并关联元素</title>
    <link href="https://blog.dappwind.com/2020/05/15/"/>
    <id>https://blog.dappwind.com/2020/05/15/</id>
    <published>2020-05-15T14:13:32.000Z</published>
    <updated>2020-07-19T16:53:59.059Z</updated>
    
    <content type="html"><![CDATA[<p>接上文，已经识别出页面元素，接下来需要识别出文字，并与元素关联</p><blockquote><p><a href="https://blog.dappwind.com/2020/05/13/index.html">https://blog.dappwind.com/2020/05/13/index.html</a></p></blockquote><h2 id="元素的文字识别"><a href="#元素的文字识别" class="headerlink" title="元素的文字识别"></a>元素的文字识别</h2><p>使用 <code>tesseract</code>的效果好很多，所以采用 <code>tesseract</code>。</p><blockquote><p>tesseract测试: <a href="https://github.com/yuxizhe/OCR/blob/master/tesseract.ipynb" target="_blank" rel="noopener">https://github.com/yuxizhe/OCR/blob/master/tesseract.ipynb</a></p></blockquote><p>如果用tesseract对整个图片进行文字识别的话，返回的是要么是全部的字符串，要么是每个字和每个字的坐标，很难与元素关联起来。</p><p>尝试了多种方式未果，后来决定采用对每个元素的图片进行单独识别，这样可以很方便的把文字与元素建立关联关系。</p><p>元素内文字的识别比较方便，直接调用识别即可。</p><p>表单元素前面的文字也是需要识别的，这里采用简单的方案，大概取元素 前300px 生成一个图片，对这个图片进行文字识别，结果就是表单元素的介绍文字。</p><p>把方法编写成function如下<br><figure class="highlight python"><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"># 识别元素内及元素前的文字</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">element_ocr</span><span class="params">(cropped, img, x, y, w, h)</span>:</span></span><br><span class="line">  <span class="comment"># 识别矩形中的文字</span></span><br><span class="line">  inner_text = pytesseract.image_to_string(cropped, lang=<span class="string">'chi_sim'</span>)</span><br><span class="line">  print(<span class="string">'元素内文字:  '</span>+ inner_text)</span><br><span class="line">  </span><br><span class="line">  <span class="comment"># 识别矩形前的文字 往前300</span></span><br><span class="line">  textAreaBefore = img[y:y+h,max(<span class="number">0</span>,x<span class="number">-300</span>):x]</span><br><span class="line">  cv2_imshow(textAreaBefore)</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 识别前序文字</span></span><br><span class="line">  before_text = pytesseract.image_to_string(textAreaBefore, lang=<span class="string">'chi_sim'</span>)</span><br><span class="line">  print(<span class="string">'元素前区域文字：  '</span>+before_text)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> inner_text,before_text</span><br></pre></td></tr></table></figure></p><p>结果打印如下<br><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line">组件<span class="number">0</span>: Input ：     before：*Bundle id:</span><br><span class="line">组件<span class="number">1</span>: Button ：     before：* 更新信息:</span><br><span class="line">组件<span class="number">2</span>: Input ：请输入版本号 (例如: <span class="number">11.31</span>)     before：* 版本号:</span><br><span class="line">组件<span class="number">3</span>: Input ：ioS     before：* 平台:</span><br><span class="line">组件<span class="number">4</span>: Input ：雪球     before：* APP名:</span><br><span class="line">组件<span class="number">5</span>: Button ：出 点击上传 ipa 文件     before：* 上传ipa 文件:</span><br><span class="line">组件<span class="number">6</span>: Input ：Public     before：* 包类型:</span><br></pre></td></tr></table></figure></p><h2 id="将文字显示在图片上"><a href="#将文字显示在图片上" class="headerlink" title="将文字显示在图片上"></a>将文字显示在图片上</h2><p>cv2 的 <code>putText</code>方法不支持直接显示中文，会出现问号乱码</p><p>参考这篇文章</p><blockquote><p><a href="https://www.cnblogs.com/vipstone/p/8998249.html" target="_blank" rel="noopener">https://www.cnblogs.com/vipstone/p/8998249.html</a></p></blockquote><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 中文字体显示</span></span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image, ImageDraw, ImageFont</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">cv2ImgAddText</span><span class="params">(img, text, left, top, textColor=<span class="params">(<span class="number">255</span>, <span class="number">0</span>, <span class="number">255</span>)</span>, textSize=<span class="number">15</span>)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> (isinstance(img, np.ndarray)):  <span class="comment">#判断是否OpenCV图片类型</span></span><br><span class="line">        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))</span><br><span class="line">    draw = ImageDraw.Draw(img)</span><br><span class="line">    fontText = ImageFont.truetype(</span><br><span class="line">        <span class="string">"/content/PingFang.ttc"</span>, textSize, encoding=<span class="string">"utf-8"</span>)</span><br><span class="line">    draw.text((left, top), text, textColor, font=fontText)</span><br><span class="line">    <span class="keyword">return</span> cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)</span><br></pre></td></tr></table></figure><p>把展示识别结果的函数修改为</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在图片上显示结果</span></span><br><span class="line"><span class="comment"># 通过ImageDataGenerator.flow 后 顺序会变化</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">result_show_in_image</span><span class="params">(img, boxes, predicted_name)</span>:</span></span><br><span class="line">  newImage = img.copy()</span><br><span class="line">  <span class="keyword">for</span> index <span class="keyword">in</span> range(len(boxes)):</span><br><span class="line">    item = boxes[index]</span><br><span class="line">    box = item[<span class="number">0</span>]</span><br><span class="line">    name = predicted_name[index]</span><br><span class="line">    text = <span class="string">'组件: '</span> + name + <span class="string">' ：'</span>+ item[<span class="number">1</span>] + <span class="string">'     before：'</span>+ item[<span class="number">2</span>]</span><br><span class="line">    print(<span class="string">'组件'</span>+ str(index) + <span class="string">': '</span> + text)</span><br><span class="line">    <span class="comment"># 中文字体显示</span></span><br><span class="line">    newImage = cv2ImgAddText(newImage, text, min(box[<span class="number">2</span>][<span class="number">0</span>],box[<span class="number">0</span>][<span class="number">0</span>]) + <span class="number">10</span>, max(box[<span class="number">2</span>][<span class="number">1</span>],box[<span class="number">0</span>][<span class="number">1</span>]) - <span class="number">30</span>)</span><br><span class="line">    <span class="comment"># cv2.putText(newImage, name, (min(box[2][0],box[0][0]) + 10, max(box[2][1],box[0][1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (225,0,225), 1)</span></span><br><span class="line">  cv2_imshow(newImage)</span><br></pre></td></tr></table></figure><p>结果如下</p><p><img src="https://g.dappwind.com/static/20200515215044.png" alt><br><img src="https://g.dappwind.com/static/20200515215139.png" alt><br><img src="https://g.dappwind.com/static/20200515215333.png" alt><br><img src="https://g.dappwind.com/static/20200515215411.png" alt></p>]]></content>
    
    <summary type="html">
    
      接上文，已经识别出页面元素，接下来需要识别出文字，并与元素关联

https://blog.dappwind.com/2020/05/13/index.html

元素的文字识别
使用 tesseract的效果好很多，所以采用 tesseract。

tesseract测试: https://github.com/yuxizhe/OCR/blob/master/tesseract.ipynb

如
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>机器学习生成CRM表单 - 2.opencv提取后模型分类</title>
    <link href="https://blog.dappwind.com/2020/05/14/"/>
    <id>https://blog.dappwind.com/2020/05/14/</id>
    <published>2020-05-14T07:10:37.000Z</published>
    <updated>2020-07-19T16:54:00.845Z</updated>
    
    <content type="html"><![CDATA[<h2 id="opencv提取图片中的矩形"><a href="#opencv提取图片中的矩形" class="headerlink" title="opencv提取图片中的矩形"></a>opencv提取图片中的矩形</h2><p>opencv 提取图片中的矩形代码</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">from</span> google.colab.patches <span class="keyword">import</span> cv2_imshow</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">def</span> <span class="title">cv_get_block</span><span class="params">(location)</span>:</span></span><br><span class="line">  img = cv2.imread(location)</span><br><span class="line">  <span class="comment"># img resize to 700</span></span><br><span class="line">  img = cv2.resize(img, (<span class="number">700</span>,int(<span class="number">700</span>/img.shape[<span class="number">1</span>]*img.shape[<span class="number">0</span>])), interpolation=cv2.INTER_AREA)</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 转为灰度</span></span><br><span class="line">  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line">  <span class="comment"># 边缘检测</span></span><br><span class="line">  binary = cv2.Canny(gray,<span class="number">50</span>,<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">#cv2_imshow(binary)</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">#检测轮廓</span></span><br><span class="line">  <span class="comment"># RETR_EXTERNAL  表示只检测最外层轮廓</span></span><br><span class="line">  contours, hier = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 裁剪出的图片</span></span><br><span class="line">  croppedImage = []</span><br><span class="line">  <span class="comment"># 位置信息和文案信息</span></span><br><span class="line">  positionAndText = []</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> c <span class="keyword">in</span> contours:  <span class="comment">#遍历轮廓</span></span><br><span class="line">      rect = cv2.minAreaRect(c)  <span class="comment">#生成最小外接矩形</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># 计算最小面积矩形的坐标</span></span><br><span class="line">      <span class="comment"># 这个返回坐标点顺序是随机的</span></span><br><span class="line">      box = cv2.boxPoints(rect)</span><br><span class="line">      box = np.int0(box)  <span class="comment"># 将坐标规范化为整数</span></span><br><span class="line"></span><br><span class="line">      h = int(abs(box[<span class="number">3</span>, <span class="number">1</span>] - box[<span class="number">1</span>, <span class="number">1</span>]))</span><br><span class="line">      w = int(abs(box[<span class="number">3</span>, <span class="number">0</span>] - box[<span class="number">1</span>, <span class="number">0</span>]))</span><br><span class="line">      y = min(box[<span class="number">2</span>][<span class="number">1</span>],box[<span class="number">0</span>][<span class="number">1</span>])</span><br><span class="line">      x = min(box[<span class="number">2</span>][<span class="number">0</span>],box[<span class="number">0</span>][<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">      <span class="comment"># 去除太小太大 的矩形，只保留合适的</span></span><br><span class="line">      <span class="keyword">if</span> (h &gt; <span class="number">500</span> <span class="keyword">or</span> w &gt; <span class="number">600</span>):</span><br><span class="line">          <span class="keyword">continue</span></span><br><span class="line">      <span class="keyword">if</span> (h &lt; <span class="number">20</span> <span class="keyword">or</span> w &lt; <span class="number">60</span>):</span><br><span class="line">          <span class="keyword">continue</span></span><br><span class="line">      </span><br><span class="line">      <span class="comment">#print(h,w)</span></span><br><span class="line">      <span class="comment">#print(box)</span></span><br><span class="line">      <span class="comment"># 取出图片</span></span><br><span class="line">      <span class="comment"># image[y:y+h, x:x+w]  </span></span><br><span class="line">      cropped = img[y<span class="number">-5</span>:y+h+<span class="number">5</span>, x<span class="number">-5</span>:x+w+<span class="number">5</span>]</span><br><span class="line"></span><br><span class="line">      print(<span class="string">'\n 元素识别：'</span>)</span><br><span class="line">      cv2_imshow(cropped)</span><br><span class="line"></span><br><span class="line">      <span class="comment"># 识别矩形中的文字</span></span><br><span class="line">      inner_text, before_text = element_ocr(cropped, img, x, y, w, h)</span><br><span class="line">      </span><br><span class="line">      <span class="comment"># 传出</span></span><br><span class="line">      positionAndText.append([box,inner_text,before_text])</span><br><span class="line">      </span><br><span class="line">      <span class="comment"># 格式化为正方形</span></span><br><span class="line">      cropped = resize_image(cropped)</span><br><span class="line">      croppedImage.append(cropped)</span><br><span class="line">      <span class="comment"># 绘制矩形</span></span><br><span class="line">      cv2.drawContours(img, [box], <span class="number">0</span>, (<span class="number">255</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">1</span>)</span><br><span class="line">  <span class="comment">#cv2_imshow(img)</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 输出 array 而不是list </span></span><br><span class="line">  <span class="comment"># 'Input data in `NumpyArrayIterator` should have rank 4. You passed an array with shape', (224, 224, 3))</span></span><br><span class="line">  <span class="comment"># 因为 ImageDataGenerator.flow 输入为 NumpyArray</span></span><br><span class="line">  <span class="keyword">return</span> np.array(croppedImage), np.array(positionAndText), img</span><br></pre></td></tr></table></figure><p>代码中的关键函数</p><h3 id="找轮廓-findContours"><a href="#找轮廓-findContours" class="headerlink" title="找轮廓 findContours"></a>找轮廓 findContours</h3><p><code>findContours</code> 找轮廓</p><blockquote><p><a href="https://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=findcontours#findcontours" target="_blank" rel="noopener">https://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=findcontours#findcontours</a></p></blockquote><p>本例中使用</p><figure class="highlight python"><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="comment"># 边缘检测</span></span><br><span class="line">binary = cv2.Canny(gray,<span class="number">50</span>,<span class="number">100</span>)</span><br><span class="line">contours, hier = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)</span><br></pre></td></tr></table></figure><h3 id="最小外接矩形-minAreaRect"><a href="#最小外接矩形-minAreaRect" class="headerlink" title="最小外接矩形 minAreaRect"></a>最小外接矩形 minAreaRect</h3><p><code>minAreaRect</code> 最小外接矩形</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rect = cv2.minAreaRect(c)  <span class="comment">#生成最小外接矩形</span></span><br></pre></td></tr></table></figure><h3 id="boxPoints-返回矩形坐标"><a href="#boxPoints-返回矩形坐标" class="headerlink" title="boxPoints 返回矩形坐标"></a>boxPoints 返回矩形坐标</h3><p>cv2.boxPoints 返回的坐标顺序：<br><code>注意</code>：需要注意的是，这个函数返回的坐标是没有严格顺序的，不能根据index来确定，所以取坐标位置时，要用min,max等函数<br><figure class="highlight python"><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">h = abs(box[<span class="number">3</span>, <span class="number">1</span>] - box[<span class="number">1</span>, <span class="number">1</span>])</span><br><span class="line">w = abs(box[<span class="number">3</span>, <span class="number">0</span>] - box[<span class="number">1</span>, <span class="number">0</span>])</span><br></pre></td></tr></table></figure></p><h3 id="image-裁剪图片"><a href="#image-裁剪图片" class="headerlink" title="image 裁剪图片"></a>image 裁剪图片</h3><p><code>image[y:y+h, x:x+w]</code><br>因为因为boxPoints返回坐标点顺序是随机的，无法使用顺序计算，需要用min max 取一下<br><figure class="highlight python"><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="comment"># 取出图片</span></span><br><span class="line"><span class="comment"># image[y:y+h, x:x+w]</span></span><br><span class="line"><span class="comment"># 因为boxPoints返回坐标点顺序是随机的，无法使用顺序计算</span></span><br><span class="line">cropped = img[</span><br><span class="line">    min(box[<span class="number">2</span>][<span class="number">1</span>],box[<span class="number">0</span>][<span class="number">1</span>])<span class="number">-5</span>:max(box[<span class="number">2</span>][<span class="number">1</span>],box[<span class="number">0</span>][<span class="number">1</span>])+<span class="number">5</span>,</span><br><span class="line">    min(box[<span class="number">2</span>][<span class="number">0</span>],box[<span class="number">0</span>][<span class="number">0</span>])<span class="number">-5</span>:max(box[<span class="number">2</span>][<span class="number">0</span>],box[<span class="number">0</span>][<span class="number">0</span>])+<span class="number">5</span>]</span><br></pre></td></tr></table></figure></p><h2 id="截取的图片进一步处理"><a href="#截取的图片进一步处理" class="headerlink" title="截取的图片进一步处理"></a>截取的图片进一步处理</h2><p>使用 <code>ImageDataGenerator.flow</code></p><blockquote><p><a href="https://keras.io/api/preprocessing/image/" target="_blank" rel="noopener">https://keras.io/api/preprocessing/image/</a></p></blockquote><p>图片的shape是 (224, 224, 3)</p><p>flow输入要求为</p><blockquote><p>NumPy array of rank 4 or a tuple. If tuple, the first element should contain the images and the second element another NumPy array or a list of NumPy arrays that gets passed to the output without any modifications. Can be used to feed the model miscellaneous data along with the images. In case of grayscale data, the channels axis of the image array should have value 1, in case of RGB data, it should have value 3, and in case of RGBA data, it should have value 4.</p></blockquote><p>flow 要求的输入shape是4阶 NumPy array，python中 list 和 num array 是不一样的，所以</p><p>需要 <code>np.array(croppedImage)</code> 把list 转为 array</p><p><code>注意</code>：还有需要注意的一点是，<code>ImageDataGenerator</code> 返回的数组并不是按照输入的顺序，这点比较坑，需要用label传递对应关系。因为预测完后，还需要这个元素的坐标，才能正确对应。所以这里把坐标传到了label中，得到预期的对应关系。</p><figure class="highlight python"><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="comment"># create a data generator</span></span><br><span class="line">datagen = ImageDataGenerator(rescale=(<span class="number">1</span>/<span class="number">255</span>))</span><br><span class="line"><span class="comment"># 矩形元素 通过ImageDataGenerator 图像进一步处理</span></span><br><span class="line"><span class="comment"># 需要注意的是，通过flow输出的数据是乱序的，并不会严格安装输入的顺序，所以对应关系要存储在label中</span></span><br><span class="line">input_data = datagen.flow(images, boxes)</span><br><span class="line">image_batch, boxes_batch = next(input_data)</span><br></pre></td></tr></table></figure><h2 id="加载训练好的模型"><a href="#加载训练好的模型" class="headerlink" title="加载训练好的模型"></a>加载训练好的模型</h2><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 加载模型</span></span><br><span class="line">html_model = tf.keras.models.load_model(<span class="string">'/content/model/'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 预测元素</span></span><br><span class="line">predicted_batch = html_model.predict(image_batch)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 结果归一</span></span><br><span class="line">predicted_id = np.argmax(predicted_batch, axis=<span class="number">-1</span>)</span><br><span class="line"></span><br><span class="line">class_name = np.array([<span class="string">'Input'</span>, <span class="string">'Select'</span>, <span class="string">'Button'</span>])</span><br><span class="line"><span class="comment"># 结果对应上名字</span></span><br><span class="line">predicted_name = class_name[predicted_id]</span><br><span class="line">print(predicted_name)</span><br><span class="line"><span class="comment"># 图示结果</span></span><br><span class="line">result_show(image_batch, predicted_name)</span><br></pre></td></tr></table></figure><h2 id="结果"><a href="#结果" class="headerlink" title="结果"></a>结果</h2><p>结果如下</p><p><img src="https://g.dappwind.com/static/20200515164638.png" alt><br><img src="https://g.dappwind.com/static/20200515171208.png" alt><br><img src="https://g.dappwind.com/static/20200515172753.png" alt><br><img src="https://g.dappwind.com/static/20200515172904.png" alt></p><h2 id="待提高"><a href="#待提高" class="headerlink" title="待提高"></a>待提高</h2><p>可见识别准确度还有提高的空间，因为模型只训练了一次，数据也少，后续可以进一步提高模型的准确度。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>完整代码如下</p><blockquote><p><a href="https://github.com/yuxizhe/HTML-UI-datasets-generate/blob/master/cv%E5%90%8E%E5%88%86%E7%B1%BB.ipynb" target="_blank" rel="noopener">https://github.com/yuxizhe/HTML-UI-datasets-generate/blob/master/cv%E5%90%8E%E5%88%86%E7%B1%BB.ipynb</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      opencv提取图片中的矩形
opencv 提取图片中的矩形代码

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
5
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>机器学习生成CRM表单 - 1.模型训练</title>
    <link href="https://blog.dappwind.com/2020/05/13/"/>
    <id>https://blog.dappwind.com/2020/05/13/</id>
    <published>2020-05-13T10:08:03.000Z</published>
    <updated>2020-07-19T16:54:03.210Z</updated>
    
    <content type="html"><![CDATA[<h1 id="表单自动生成-机器学习-模型训练"><a href="#表单自动生成-机器学习-模型训练" class="headerlink" title="表单自动生成 机器学习 模型训练"></a>表单自动生成 机器学习 模型训练</h1><h2 id="数据获取"><a href="#数据获取" class="headerlink" title="数据获取"></a>数据获取</h2><p>按照HTML表单元素分类，进行截图。因为这里不是目标识别，只是分类，所以自动生成作用不太大。</p><p>目录按照如下编排，每个类别建立一个文件夹，里面放相应的截图图片，方便后续处理，对图片形状大小不做要求。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">main_directory/</span><br><span class="line">...class_a/</span><br><span class="line">......a_image_1.jpg</span><br><span class="line">......a_image_2.jpg</span><br><span class="line">...class_b/</span><br><span class="line">......b_image_1.jpg</span><br><span class="line">......b_image_2.jpg</span><br></pre></td></tr></table></figure><h2 id="数据输入处理"><a href="#数据输入处理" class="headerlink" title="数据输入处理"></a>数据输入处理</h2><h4 id="图像预处理"><a href="#图像预处理" class="headerlink" title="图像预处理"></a>图像预处理</h4><p>将图片文件预处理后生成浮点数张量，步骤如下：</p><ul><li>（1）读取图像文件</li><li>（2）将文件解码为RGB像素网格</li><li>（3）将像素网格转换为浮点数张量</li><li>（4）将像素值（0-255）缩放到（0-1）区间</li></ul><p>可以使用python手写这些代码，keras有个预处理函数，可以实现上述步骤。</p><blockquote><p><a href="https://keras.io/api/preprocessing/image/#flow_from_directory-method" target="_blank" rel="noopener">https://keras.io/api/preprocessing/image/#flow_from_directory-method</a></p></blockquote><p>我们这样使用</p><figure class="highlight python"><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="comment"># create a data generator</span></span><br><span class="line">datagen = ImageDataGenerator(rescale=(<span class="number">1</span>/<span class="number">255</span>))</span><br><span class="line">train_data = datagen.flow_from_directory(<span class="string">'图片目录'</span>, target_size=(<span class="number">224</span>,<span class="number">224</span>))</span><br></pre></td></tr></table></figure><p><code>target_size</code> 参数会对图片进行预处理，处理为目标大小。这里有个问题，因为我们的html表单元素大部分是长方形的，而这个图像处理是不保持宽高比的，所以元素的形状会改变为正方形，必然会造成信息丢失。</p><p>其实很多人发现这个问题并提了PR </p><blockquote><p><a href="https://stackoverflow.com/questions/42467734/keras-how-to-use-imagedatagenerator-without-deforming-aspect-ratio" target="_blank" rel="noopener">https://stackoverflow.com/questions/42467734/keras-how-to-use-imagedatagenerator-without-deforming-aspect-ratio</a></p></blockquote><blockquote><p><a href="https://github.com/keras-team/keras-preprocessing/pull/81" target="_blank" rel="noopener">https://github.com/keras-team/keras-preprocessing/pull/81</a></p></blockquote><blockquote><p><a href="https://github.com/keras-team/keras/pull/4987" target="_blank" rel="noopener">https://github.com/keras-team/keras/pull/4987</a></p></blockquote><p>但是3年过去没和进去。（有空可以提个PR增加这个功能）</p><p>所以目前解决方案需要自己处理。</p><h4 id="ImageDataGenerator-保持宽高比-扩充图片为正方形"><a href="#ImageDataGenerator-保持宽高比-扩充图片为正方形" class="headerlink" title="ImageDataGenerator 保持宽高比 扩充图片为正方形"></a>ImageDataGenerator 保持宽高比 扩充图片为正方形</h4><p>参考这篇文章,把填充的内容改为白色</p><blockquote><p><a href="https://blog.csdn.net/u010397980/article/details/84889093" target="_blank" rel="noopener">https://blog.csdn.net/u010397980/article/details/84889093</a></p></blockquote><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> glob</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mkdir</span><span class="params">(path)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(path):</span><br><span class="line">        os.mkdir(path)</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">process_image</span><span class="params">(img)</span>:</span></span><br><span class="line">    size = img.shape</span><br><span class="line">    h, w = size[<span class="number">0</span>], size[<span class="number">1</span>]</span><br><span class="line">    <span class="comment">#长边缩放为min_side </span></span><br><span class="line">    scale = max(w, h) / float(min_side)</span><br><span class="line">    new_w, new_h = int(w/scale), int(h/scale)</span><br><span class="line">    resize_img = cv2.resize(img, (new_w, new_h))</span><br><span class="line">    <span class="comment"># 填充至min_side * min_side</span></span><br><span class="line">    <span class="keyword">if</span> new_w % <span class="number">2</span> != <span class="number">0</span> <span class="keyword">and</span> new_h % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        top, bottom, left, right = (min_side-new_h)/<span class="number">2</span>, (min_side-new_h)/<span class="number">2</span>, (min_side-new_w)/<span class="number">2</span> + <span class="number">1</span>, (min_side-new_w)/<span class="number">2</span></span><br><span class="line">    <span class="keyword">elif</span> new_h % <span class="number">2</span> != <span class="number">0</span> <span class="keyword">and</span> new_w % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        top, bottom, left, right = (min_side-new_h)/<span class="number">2</span> + <span class="number">1</span>, (min_side-new_h)/<span class="number">2</span>, (min_side-new_w)/<span class="number">2</span>, (min_side-new_w)/<span class="number">2</span></span><br><span class="line">    <span class="keyword">elif</span> new_h % <span class="number">2</span> == <span class="number">0</span> <span class="keyword">and</span> new_w % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        top, bottom, left, right = (min_side-new_h)/<span class="number">2</span>, (min_side-new_h)/<span class="number">2</span>, (min_side-new_w)/<span class="number">2</span>, (min_side-new_w)/<span class="number">2</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        top, bottom, left, right = (min_side-new_h)/<span class="number">2</span> + <span class="number">1</span>, (min_side-new_h)/<span class="number">2</span>, (min_side-new_w)/<span class="number">2</span> + <span class="number">1</span>, (min_side-new_w)/<span class="number">2</span></span><br><span class="line">    pad_img = cv2.copyMakeBorder(resize_img, int(top), int(bottom), int(left), int(right), cv2.BORDER_CONSTANT, value=[<span class="number">255</span>,<span class="number">255</span>,<span class="number">255</span>]) <span class="comment">#从图像边界向上,下,左,右扩的像素数目</span></span><br><span class="line">    <span class="comment">#print pad_img.shape</span></span><br><span class="line">    <span class="comment">#cv2.imwrite("after-" + os.path.basename(filename), pad_img)</span></span><br><span class="line">    <span class="keyword">return</span> pad_img</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">resize</span><span class="params">(data_dir)</span>:</span></span><br><span class="line">    save_dir = data_dir + <span class="string">"_pad_"</span> + str(min_side)</span><br><span class="line">    mkdir(save_dir)</span><br><span class="line">    num = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> label <span class="keyword">in</span> os.listdir(data_dir):</span><br><span class="line">        num += <span class="number">1</span></span><br><span class="line">        print(<span class="string">"%d/%d, label:%s"</span> %(num, len(os.listdir(data_dir)), label))</span><br><span class="line">        mkdir(os.path.join(save_dir, label))</span><br><span class="line">        <span class="keyword">for</span> img <span class="keyword">in</span> glob.glob(os.path.join(data_dir, label, <span class="string">"*.png"</span>)):</span><br><span class="line">            <span class="comment">#print img</span></span><br><span class="line">            image = cv2.imread(img)</span><br><span class="line">            <span class="keyword">if</span> type(image) == type(<span class="literal">None</span>):</span><br><span class="line">                print(<span class="string">"damaged image %s, del it"</span> %(img))</span><br><span class="line">                os.remove(img)</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            img_pad = process_image(image)</span><br><span class="line">            cv2.imwrite(os.path.join(save_dir, label, os.path.basename(img)), img_pad)</span><br></pre></td></tr></table></figure><p>打印出处理后的图片</p><figure class="highlight python"><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">def</span> <span class="title">show_batch</span><span class="params">(image_batch, label_batch)</span>:</span></span><br><span class="line">  plt.figure(figsize=(<span class="number">10</span>,<span class="number">10</span>))</span><br><span class="line">  <span class="keyword">for</span> n <span class="keyword">in</span> range(<span class="number">5</span>):</span><br><span class="line">      ax = plt.subplot(<span class="number">5</span>,<span class="number">5</span>,n+<span class="number">1</span>)</span><br><span class="line">      plt.imshow(image_batch[n].squeeze())</span><br><span class="line">      <span class="comment"># plt.title(CLASS_NAMES[int(label_batch[n])])</span></span><br><span class="line">      plt.axis(<span class="string">'off'</span>)</span><br><span class="line">image_batch, label_batch = next(train_data)</span><br><span class="line"></span><br><span class="line">show_batch(image_batch, label_batch)</span><br></pre></td></tr></table></figure><h2 id="训练"><a href="#训练" class="headerlink" title="训练"></a>训练</h2><p>把训练方案都列出来</p><h4 id="1-全连接训练模型"><a href="#1-全连接训练模型" class="headerlink" title="1.全连接训练模型"></a>1.全连接训练模型</h4><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 方法一：全连接的训练</span></span><br><span class="line">model = keras.Sequential([</span><br><span class="line">    keras.layers.Flatten(input_shape=(<span class="number">224</span>, <span class="number">224</span>)),</span><br><span class="line">    keras.layers.Dense(<span class="number">1000</span>, activation=<span class="string">'relu'</span>),</span><br><span class="line">    keras.layers.Dense(<span class="number">100</span>, activation=<span class="string">'relu'</span>),</span><br><span class="line">    keras.layers.Dense(<span class="number">6</span>)</span><br><span class="line">])</span><br><span class="line">model.summary()</span><br></pre></td></tr></table></figure><p>最基础的入门训练模型</p><h4 id="2-卷积神经网络训练模型-CNN"><a href="#2-卷积神经网络训练模型-CNN" class="headerlink" title="2.卷积神经网络训练模型 CNN"></a>2.卷积神经网络训练模型 CNN</h4><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line">model = models.Sequential()</span><br><span class="line">model.add(layers.Conv2D(<span class="number">32</span>, (<span class="number">3</span>, <span class="number">3</span>), activation=<span class="string">'relu'</span>, input_shape=(<span class="number">100</span>, <span class="number">100</span>, <span class="number">1</span>)))</span><br><span class="line">model.add(layers.MaxPooling2D((<span class="number">2</span>, <span class="number">2</span>)))</span><br><span class="line">model.add(layers.Conv2D(<span class="number">64</span>, (<span class="number">3</span>, <span class="number">3</span>), activation=<span class="string">'relu'</span>)) </span><br><span class="line">model.add(layers.MaxPooling2D((<span class="number">2</span>, <span class="number">2</span>)))</span><br><span class="line">model.add(layers.Conv2D(<span class="number">64</span>, (<span class="number">3</span>, <span class="number">3</span>), activation=<span class="string">'relu'</span>))</span><br><span class="line"></span><br><span class="line">model.add(layers.Flatten())</span><br><span class="line">model.add(layers.Dense(<span class="number">500</span>, activation=<span class="string">'relu'</span>))</span><br><span class="line">model.add(layers.Dense(<span class="number">3</span>, activation=<span class="string">'softmax'</span>))</span><br><span class="line">model.summary()</span><br></pre></td></tr></table></figure><p>上面两个模型的介绍可以参考<code>《python》深度学习</code>这本入门书，介绍的很详细。</p><h4 id="3-迁移学习训练模型"><a href="#3-迁移学习训练模型" class="headerlink" title="3.迁移学习训练模型"></a>3.迁移学习训练模型</h4><p>本来直接照着《python深度学习》，一步一步进行搭建model，进行训练，但是比较慢且效果不太好。</p><p>然后在谷歌的在线 tf.js 图片分类的demo页试用了下，</p><blockquote><p><a href="https://teachablemachine.withgoogle.com/train/image" target="_blank" rel="noopener">https://teachablemachine.withgoogle.com/train/image</a></p></blockquote><p>发现训练超级快而且准确，这是什么原因呢？</p><p>在github打开了网页源码：</p><blockquote><p><a href="https://github.com/googlecreativelab/teachable-machine-v1/blob/master/src/ai/WebcamClassifier.js#L346-L348" target="_blank" rel="noopener">https://github.com/googlecreativelab/teachable-machine-v1/blob/master/src/ai/WebcamClassifier.js#L346-L348</a></p></blockquote><p>发现其用了训练好的模型 <code>mobilenet</code>。</p><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="keyword">import</span> * <span class="keyword">as</span> mobilenet <span class="keyword">from</span> <span class="string">'@tensorflow-models/mobilenet'</span>;</span><br></pre></td></tr></table></figure><p>这才想起来用的是 模型复用-迁移学习。</p><h3 id="迁移学习"><a href="#迁移学习" class="headerlink" title="迁移学习"></a>迁移学习</h3><h4 id="迁移学习介绍"><a href="#迁移学习介绍" class="headerlink" title="迁移学习介绍"></a>迁移学习介绍</h4><p>以人打比方，训练好的模型，相当于一个能熟练掌握C语言的程序员，迁移学习，就是在这个模型上训练新的内容，相当于让这个有丰富C语言开发经验的人去新学习js，肯定会比没有编程经验的人学得快，即比新写模型训练快而准。</p><p>tf 的机器学习训练教程</p><blockquote><p><a href="https://www.tensorflow.org/tutorials/images/transfer_learning_with_hub" target="_blank" rel="noopener">https://www.tensorflow.org/tutorials/images/transfer_learning_with_hub</a></p></blockquote><p>注意tf的版本要使用比较新的版本，否则会报错，使用tf-nightly。</p><p>从tfpub引入训练好的模型， headless model, 就是没有最后分类层的。方便我们迁移训练。</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line">feature_extractor_url = <span class="string">"https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/2"</span> </span><br><span class="line">feature_extractor_layer = hub.KerasLayer(feature_extractor_url, input_shape=(<span class="number">224</span>, <span class="number">224</span>, <span class="number">3</span>))</span><br><span class="line"><span class="comment"># 关闭mobilenet的训练</span></span><br><span class="line">feature_extractor_layer.trainable = <span class="literal">False</span></span><br></pre></td></tr></table></figure><h4 id="建立模型"><a href="#建立模型" class="headerlink" title="建立模型"></a>建立模型</h4><figure class="highlight python"><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">model = tf.keras.Sequential([</span><br><span class="line">  feature_extractor_layer,</span><br><span class="line">  layers.Dense(<span class="number">3</span>)</span><br><span class="line">])</span><br><span class="line">model.summary()</span><br></pre></td></tr></table></figure><p>其实就是加了个最后的分类，这个例子里 最后是分为3类</p><h4 id="训练-1"><a href="#训练-1" class="headerlink" title="训练"></a>训练</h4><figure class="highlight python"><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">predictions = model(image_batch)</span><br><span class="line">model.compile(</span><br><span class="line">  optimizer=tf.keras.optimizers.Adam(),</span><br><span class="line">  loss=tf.keras.losses.CategoricalCrossentropy(from_logits=<span class="literal">True</span>),</span><br><span class="line">  metrics=[<span class="string">'acc'</span>])</span><br><span class="line">model.fit_generator(train_data, epochs=<span class="number">10</span>, steps_per_epoch=<span class="number">20</span>, validation_data=valid_data, validation_steps=<span class="number">8</span>)</span><br></pre></td></tr></table></figure><p>虽然看起来最后的模型只有简单的两层，但训练效果却很好</p><p><img src="https://g.dappwind.com/static/20200513185157.png" alt></p><h4 id="保存模型"><a href="#保存模型" class="headerlink" title="保存模型"></a>保存模型</h4><figure class="highlight python"><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="keyword">import</span> time</span><br><span class="line">t = time.time()</span><br><span class="line"></span><br><span class="line">export_path = <span class="string">"/content/model/&#123;&#125;"</span>.format(int(t))</span><br><span class="line">model.save(export_path, save_format=<span class="string">'tf'</span>)</span><br></pre></td></tr></table></figure><p>使用保存的模型<br><figure class="highlight python"><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">reloaded = tf.keras.models.load_model(export_path)</span><br><span class="line">reloaded_result_batch = reloaded.predict(image_batch)</span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      表单自动生成 机器学习 模型训练
数据获取
按照HTML表单元素分类，进行截图。因为这里不是目标识别，只是分类，所以自动生成作用不太大。

目录按照如下编排，每个类别建立一个文件夹，里面放相应的截图图片，方便后续处理，对图片形状大小不做要求。

1
2
3
4
5
6
7


main_directory/
...class_a/
......a_image_1.jpg
......a_image
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>智能生成前端代码</title>
    <link href="https://blog.dappwind.com/2020/04/20/"/>
    <id>https://blog.dappwind.com/2020/04/20/</id>
    <published>2020-04-20T14:27:03.000Z</published>
    <updated>2021-08-21T02:22:51.119Z</updated>
    
    <content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>2017 年，一篇关于图像转代码的 <a href="https://arxiv.org/abs/1705.07962" target="_blank" rel="noopener">Pix2Code</a> 论文掀起了业内激烈讨论的波澜，讲述如何从设计原型直接生成源代码。随后社区也不断涌现出基于此思想的类似 <a href="https://github.com/emilwallner/Screenshot-to-code" target="_blank" rel="noopener">Screenshot2Code</a> 的作品，2018 年微软 AI Lab 开源了草图转代码 工具 <a href="https://github.com/microsoft/ailab/tree/master/Sketch2Code" target="_blank" rel="noopener">Sketch2Code</a>，同年年底，设计稿智能生成前端代码的新秀 Yotako 也初露锋芒， 机器学习首次以不可小觑的姿态正式进入了前端开发者的视野。</p><p>最近，阿里的<a href="https://imgcook.taobao.org/" target="_blank" rel="noopener">imgcook</a>工具，吸引了人们的眼球，基本实现了从设计稿到各种前端代码的自动生成。前面那些项目，使我们对智能生成代码还是保持观望态度，而这个项目的上线，让我们确定了D2C的可行性。</p><h1 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h1><h2 id="线上页面生成"><a href="#线上页面生成" class="headerlink" title="线上页面生成"></a>线上页面生成</h2><p>目前阿里imgcook已经上线，我们整理了使用介绍文档： 设计稿生成前端业务代码。对于不需要经常修改的活动页，可以尝试使用并生成代码。对于imgcook是否始终保持免费 或者 是否能开源，我们保持观望的态度，即使做到开源，相关的模型和组件其实也无法直接使用。不过目前没有自主搭建这方面的强烈需求，可以先用着这个服务。而且短时间很难自主实现这个繁杂的工程。</p><h2 id="CRM后台页面生成"><a href="#CRM后台页面生成" class="headerlink" title="CRM后台页面生成"></a>CRM后台页面生成</h2><p>不过，相对于复杂的线上页面，CRM后台的页面相对简单，组件基本固定，没有大量的不确定组件识别问题。在前端智能化领域，特别是中后台智能方向上，表单表格的识别是非常重要的一环。因为表单表格的开发工作，占据了中后台前端开发工作量中的绝大部分，如果能够通过智能的手段，从设计稿图片秒级生成表单表格代码，那么将会是巨大的生产力提升。目前imgcook没有开放出这部分，而雪球有大量的CRM，这个是我们目前可以做的方向。</p><h1 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h1><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171dae8719a5a996~tplv-t2oaga2asx-image.image" alt><br>对于CRM后台需求，实现从原型稿截图生成antd前端代码，目标如下图（来自imgcook的博客，只是个动图，功能没有对外开放）：</p><h1 id="整体方案"><a href="#整体方案" class="headerlink" title="整体方案"></a>整体方案</h1><ul><li><p>1.检测出所有的组件类型及其坐标。</p></li><li><p>2.通过文字识别技术和自动翻译技术识别出所有文字及其坐标并翻译为英文。</p></li><li><p>3.通过代码转换器从上述组件信息和文字信息中提取表单/表格的布局、label、type 、字段等各种属性。</p></li></ul><h1 id="组件检测方案"><a href="#组件检测方案" class="headerlink" title="组件检测方案"></a>组件检测方案</h1><h2 id="方案1-机器学习目标识别"><a href="#方案1-机器学习目标识别" class="headerlink" title="方案1.机器学习目标识别"></a>方案1.机器学习目标识别</h2><h4 id="样本集生成"><a href="#样本集生成" class="headerlink" title="样本集生成"></a>样本集生成</h4><p>机器学习的最重要的是数据集的收集和处理，HTML元素如果采用人工标注来收集处理，会浪费大量人力与时间。因此编写了个代码来自动生成HTML UI 组件数据集。</p><p><code>HTML-COCO-datasets-generate</code></p><blockquote><p><a href="https://github.com/yuxizhe/HTML-COCO-datasets-generate" target="_blank" rel="noopener">https://github.com/yuxizhe/HTML-COCO-datasets-generate</a></p></blockquote><p>可批量自动生成图片，每张图上包括25个 HTML 元素，相应的标注为coco数据格式，放都在 json中。</p><p>实现方式：</p><ul><li><p>react 项目负责生成网页</p></li><li><p>puppeteer 负责生成图片并js生成coco格式标注信息</p></li></ul><p>我们批量生成100张训练图片，10张验证图片，10张测试图片。</p><p>每个生成的图片示意如下面两<br><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daea2ac5deb03~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daeb8bc7a6f49~tplv-t2oaga2asx-image.image" alt></p><h4 id="目标检测"><a href="#目标检测" class="headerlink" title="目标检测"></a>目标检测</h4><p>目标检测框架可以理解成是把目标检测算法整合在一起的一个库。其实TensorFlow 不是一个目标检测框架，但它提供目标检测的 API： Object Detection API。</p><p>目标检测框架主要有：Detectron、maskrcnn-benchmark、mmdetection、Detectron2。目前使用较广的是Facebook AI 研究院于 2019 年 10 月 10 日开源的 Detectron2 目标检测框架。效果不错。</p><p>我们使用 Detectron2。</p><h4 id="Detectron2目标检测"><a href="#Detectron2目标检测" class="headerlink" title="Detectron2目标检测"></a>Detectron2目标检测</h4><p>在官方例子上修改，在谷歌colab上进行训练，显卡为 NVIDIA® Tesla® P100 16G。模型选择及参数如下，显卡内存占用约5G，5分钟训练完成<br><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line">cfg = get_cfg()</span><br><span class="line">cfg.merge_from_file(model_zoo.get_config_file(<span class="string">"COCO-Detection/faster_rcnn_R_50_C4_1x.yaml"</span>))</span><br><span class="line">cfg.DATASETS.TRAIN = (<span class="string">"my_dataset_train"</span>,<span class="string">"my_dataset_valid"</span>)</span><br><span class="line">cfg.DATASETS.TEST = ()</span><br><span class="line">cfg.DATALOADER.NUM_WORKERS = <span class="number">2</span></span><br><span class="line">cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(<span class="string">"COCO-Detection/faster_rcnn_R_50_C4_1x.yaml"</span>)</span><br><span class="line">cfg.SOLVER.IMS_PER_BATCH = <span class="number">2</span></span><br><span class="line">cfg.SOLVER.BASE_LR = <span class="number">0.00025</span></span><br><span class="line">cfg.SOLVER.MAX_ITER = <span class="number">500</span></span><br><span class="line">cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = <span class="number">128</span></span><br><span class="line">cfg.MODEL.ROI_HEADS.NUM_CLASSES = <span class="number">4</span></span><br></pre></td></tr></table></figure></p><p>完整训练程序及结果如下</p><blockquote><p><a href="https://github.com/yuxizhe/HTML-COCO-datasets-generate/blob/master/detectron2_html.ipynb" target="_blank" rel="noopener">https://github.com/yuxizhe/HTML-COCO-datasets-generate/blob/master/detectron2_html.ipynb</a></p></blockquote><p>能正确识别出训练目标</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daeb514d07309~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daeb65e3a344e~tplv-t2oaga2asx-image.image" alt></p><p>通过不断改进和训练，对于自身生成的组件能识别的不错，但是对于真实原型图识别效果有错误出现，如下图所示。后续可改为在真实原型图上标注的方式，获得样本数据集，提高训练结果准确性。</p><h2 id="方案2-openCV-识别矩形-机器学习图像分类"><a href="#方案2-openCV-识别矩形-机器学习图像分类" class="headerlink" title="方案2.openCV 识别矩形 + 机器学习图像分类"></a>方案2.openCV 识别矩形 + 机器学习图像分类</h2><p>用机器学习需要大量的训练样本和人力投入，观察上述检测结果，发现其对组件矩形边框这个必要条件都忽略了。因为我们的对象是CRM原型图，所有组件基本都是矩形，所以直接用openCV 提取出所有的矩形，再进行分类。会比上面的机器学习快。</p><p>分类问题的复杂度比目标检测容易很多。</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">from</span> google.colab.patches <span class="keyword">import</span> cv2_imshow</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">8</span>):</span><br><span class="line">  img = cv2.imread(<span class="string">'/content/pic/crm/'</span>+str(i + <span class="number">1</span>)+<span class="string">'.png'</span>)</span><br><span class="line">  <span class="comment"># img resize to 700</span></span><br><span class="line">  img = cv2.resize(img, (<span class="number">700</span>,int(<span class="number">700</span>/img.shape[<span class="number">1</span>]*img.shape[<span class="number">0</span>])), interpolation=cv2.INTER_AREA)</span><br><span class="line">  <span class="comment">#cv2_imshow(img)</span></span><br><span class="line">  <span class="comment"># 转为灰度</span></span><br><span class="line">  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)</span><br><span class="line">  <span class="comment"># 边缘检测</span></span><br><span class="line">  binary = cv2.Canny(gray,<span class="number">50</span>,<span class="number">100</span>)</span><br><span class="line">  <span class="comment">#ret,binary = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)</span></span><br><span class="line"></span><br><span class="line">  cv2_imshow(binary)</span><br><span class="line"></span><br><span class="line">  <span class="comment">#检测轮廓</span></span><br><span class="line">  <span class="comment"># RETR_EXTERNAL  表示只检测最外层轮廓</span></span><br><span class="line">  <span class="comment">#print(cv2.MinAreaRect2(binary))</span></span><br><span class="line">  contours, hier = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)</span><br><span class="line">  <span class="comment">#print(contours)</span></span><br><span class="line">  trueBoxes = []</span><br><span class="line">  <span class="keyword">for</span> c <span class="keyword">in</span> contours:  <span class="comment">#遍历轮廓</span></span><br><span class="line">      rect = cv2.minAreaRect(c)  <span class="comment">#生成最小外接矩形</span></span><br><span class="line">      box_ = cv2.boxPoints(rect)</span><br><span class="line">      h = abs(box_[<span class="number">3</span>, <span class="number">1</span>] - box_[<span class="number">1</span>, <span class="number">1</span>])</span><br><span class="line">      w = abs(box_[<span class="number">3</span>, <span class="number">0</span>] - box_[<span class="number">1</span>, <span class="number">0</span>])</span><br><span class="line">      <span class="comment">#print("宽，高",w,h)</span></span><br><span class="line">      <span class="comment">#只保留需要的轮廓</span></span><br><span class="line">      <span class="keyword">if</span> (h &gt; <span class="number">500</span> <span class="keyword">or</span> w &gt; <span class="number">600</span>):</span><br><span class="line">          <span class="keyword">continue</span></span><br><span class="line">      <span class="keyword">if</span> (h &lt; <span class="number">10</span> <span class="keyword">or</span> w &lt; <span class="number">50</span>):</span><br><span class="line">          <span class="keyword">continue</span></span><br><span class="line">      <span class="comment">#print("宽，高",w,h)</span></span><br><span class="line">      trueBoxes.append(box_)</span><br><span class="line">      box = cv2.boxPoints(rect)  <span class="comment"># 计算最小面积矩形的坐标</span></span><br><span class="line">      box = np.int0(box)  <span class="comment"># 将坐标规范化为整数</span></span><br><span class="line">      <span class="comment"># 绘制矩形</span></span><br><span class="line">      cv2.drawContours(img, [box], <span class="number">0</span>, (<span class="number">255</span>, <span class="number">0</span>, <span class="number">255</span>), <span class="number">3</span>)</span><br><span class="line">  cv2_imshow(img)</span><br><span class="line">  print(<span class="string">"矩形元素个数"</span>, len(trueBoxes))</span><br><span class="line">  <span class="comment">#print(trueBoxes)</span></span><br></pre></td></tr></table></figure><p>使用opencv 识别效果如下</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daed728b22f85~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daed9e79264ec~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daedb70613c41~tplv-t2oaga2asx-image.image" alt></p><p>分离出矩形后，再进行图像分类即可，TensorFlow可以很方便实现。</p><h2 id="文字识别方案"><a href="#文字识别方案" class="headerlink" title="文字识别方案"></a>文字识别方案</h2><p>可以调用收费api或者开源库来识别，这里分别使用开源库 cnocr 和 tesseract 对我们的原型图识别测试，代码及结果如下</p><blockquote><p>cnocr:  <a href="https://github.com/yuxizhe/OCR/blob/master/cnocr.ipynb" target="_blank" rel="noopener">https://github.com/yuxizhe/OCR/blob/master/cnocr.ipynb</a></p></blockquote><blockquote><p>tesseract: <a href="https://github.com/yuxizhe/OCR/blob/master/tesseract.ipynb" target="_blank" rel="noopener">https://github.com/yuxizhe/OCR/blob/master/tesseract.ipynb</a></p></blockquote><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daee36b71f683~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daee46cf0cb22~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171daee53e5605ce~tplv-t2oaga2asx-image.image" alt></p><p>使用 tesseract  的效果好很多，所以采用 tesseract。</p>]]></content>
    
    <summary type="html">
    
      背景
2017 年，一篇关于图像转代码的 Pix2Code 论文掀起了业内激烈讨论的波澜，讲述如何从设计原型直接生成源代码。随后社区也不断涌现出基于此思想的类似 Screenshot2Code 的作品，2018 年微软 AI Lab 开源了草图转代码 工具 Sketch2Code，同年年底，设计稿智能生成前端代码的新秀 Yotako 也初露锋芒， 机器学习首次以不可小觑的姿态正式进入了前端开发者的
    
    </summary>
    
    
      <category term="智能生成,前端代码,AI,Design2Code,深度学习，目标检测" scheme="https://blog.dappwind.com/tags/%E6%99%BA%E8%83%BD%E7%94%9F%E6%88%90-%E5%89%8D%E7%AB%AF%E4%BB%A3%E7%A0%81-AI-Design2Code-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%EF%BC%8C%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B/"/>
    
  </entry>
  
  <entry>
    <title>Practice of Flutter Desktop</title>
    <link href="https://blog.dappwind.com/2020/03/18/"/>
    <id>https://blog.dappwind.com/2020/03/18/</id>
    <published>2020-03-18T12:41:18.000Z</published>
    <updated>2021-08-21T02:22:51.119Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Practice-of-Flutter-Desktop"><a href="#Practice-of-Flutter-Desktop" class="headerlink" title="Practice of Flutter Desktop"></a>Practice of Flutter Desktop</h1><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9c786a6106cd~tplv-t2oaga2asx-image.image" alt></p><blockquote><p><a href="https://flutter.dev/desktop" target="_blank" rel="noopener">https://flutter.dev/desktop</a></p></blockquote><h2 id="Background"><a href="#Background" class="headerlink" title="Background"></a>Background</h2><p>We were going to build a cross-platform desktop app, there are two choices: native and electron. Because  I have used Flutter before, I remember that Flutter can also build cross-platform desktop apps, so I want to have a try. </p><p>The introduce and practice of flutter can be found in the article:</p><blockquote><p><a href="https://blog.dappwind.com/2019/09/01/index.html">https://blog.dappwind.com/2019/09/01/index.html</a></p></blockquote><h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><p>I have already compiled a flutter desktop example, you can download and have an experience.</p><p>macOS: </p><blockquote><p><a href="https://file.dappwind.com/snb_store_flutter.zip" target="_blank" rel="noopener">https://file.dappwind.com/snb_store_flutter.zip</a></p></blockquote><p>windows: </p><h2 id="Experience"><a href="#Experience" class="headerlink" title="Experience"></a>Experience</h2><p>Very smooth and the interactive experience comparable to a mobile APP.</p><h2 id="Development-efficiency"><a href="#Development-efficiency" class="headerlink" title="Development efficiency"></a>Development efficiency</h2><p>Needed to be written in Dart language.</p><p>Most of the Flutter UI frameworks are currently designed for the mobile. If you want to use it on the desktop, you need to modify some style codes. We cannot use web UI frameworks such as Ant Design directly.</p><p>But if in the future your mobile app is also written in flutter, lots of code reuse can be achieved. Taking “xuegao” as an example, the original app flutter code can directly generate a desktop app without any change.</p><h2 id="Progress"><a href="#Progress" class="headerlink" title="Progress"></a>Progress</h2><p>project is still under development, the mac version has been merged into the main branch. Windows or Linux platforms, which are in technical preview. You can try Windows and Linux platform support by reading the Desktop shells page in the Flutter wiki.</p><h2 id="Ecology"><a href="#Ecology" class="headerlink" title="Ecology"></a>Ecology</h2><p>The current flutter ecology is not as good as npm, and the desktop version has not been used by large companies. There are few third-party packages, but some basic packages can be found, For example, the stock chart </p><blockquote><p><a href="https://pub.dev/packages/syncfusion_flutter_charts" target="_blank" rel="noopener">https://pub.dev/packages/syncfusion_flutter_charts</a></p></blockquote><h2 id="Package-size"><a href="#Package-size" class="headerlink" title="Package size"></a>Package size</h2><p>The empty project generation package is about 60M, the “xuegao” installation package is 67M, and after zip compression is 20M</p><h2 id="Menu-bar-settings"><a href="#Menu-bar-settings" class="headerlink" title="Menu bar settings"></a>Menu bar settings</h2><blockquote><p><a href="https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar" target="_blank" rel="noopener">https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar</a></p></blockquote><p>Currently supports mac navigation bar settings and shortcut keys.</p><p>Windows is still under development:</p><blockquote><p><a href="https://github.com/google/flutter-desktop-embedding/issues/105" target="_blank" rel="noopener">https://github.com/google/flutter-desktop-embedding/issues/105</a></p></blockquote><h2 id="System-notification"><a href="#System-notification" class="headerlink" title="System notification"></a>System notification</h2><p>Not found</p><h2 id="File-operations"><a href="#File-operations" class="headerlink" title="File operations"></a>File operations</h2><p>Support, need permission</p><h2 id="Auto-update"><a href="#Auto-update" class="headerlink" title="Auto update"></a>Auto update</h2><p>Not provided by the official, you need to set up on your own.</p><p><a href="https://github.com/google/flutter-desktop-embedding/issues/469" target="_blank" rel="noopener">https://github.com/google/flutter-desktop-embedding/issues/469</a></p>]]></content>
    
    <summary type="html">
    
      Practice of Flutter Desktop


https://flutter.dev/desktop

Background
We were going to build a cross-platform desktop app, there are two choices: native and electron. Because I have used Flutter befor
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Flutter桌面端调研</title>
    <link href="https://blog.dappwind.com/2020/03/17/"/>
    <id>https://blog.dappwind.com/2020/03/17/</id>
    <published>2020-03-17T12:41:18.000Z</published>
    <updated>2021-08-21T02:22:51.119Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Flutter桌面端调研"><a href="#Flutter桌面端调研" class="headerlink" title="Flutter桌面端调研"></a>Flutter桌面端调研</h1><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9c786a6106cd~tplv-t2oaga2asx-image.image" alt></p><p>官方文档</p><blockquote><p><a href="https://flutter.dev/desktop" target="_blank" rel="noopener">https://flutter.dev/desktop</a></p></blockquote><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>我们要做桌面版的客户端，有原生和RN两种选择。因为之前开发过flutter，记得flutter也支持桌面端，所以就试用了下。</p><p>flutter的介绍及开发见这篇文章</p><blockquote><p><a href="https://blog.dappwind.com/2019/09/01/index.html">https://blog.dappwind.com/2019/09/01/index.html</a></p></blockquote><h1 id="打包好的Demo例子"><a href="#打包好的Demo例子" class="headerlink" title="打包好的Demo例子"></a>打包好的Demo例子</h1><p>雪糕应用广场 桌面端</p><p>mac版下载:  </p><blockquote><p><a href="https://tx-1256842980.cos.ap-beijing.myqcloud.com/test/snb_store_flutter.zip" target="_blank" rel="noopener">https://tx-1256842980.cos.ap-beijing.myqcloud.com/test/snb_store_flutter.zip</a></p></blockquote><p>windows: </p><h1 id="体验"><a href="#体验" class="headerlink" title="体验"></a>体验</h1><p>很流畅，交互效果媲美app。</p><h1 id="开发效率"><a href="#开发效率" class="headerlink" title="开发效率"></a>开发效率</h1><p>需要用dart语言写。</p><p>目前materal UI大部分是为移动端设计的，如果想桌面端使用的话，需要修改。无法使用常见的web ui框架。</p><p>不过如果以后app也接入flutter的话，可以实现大量的代码复用。以“雪糕”为例，原有的app代码直接可以生成桌面版。</p><h1 id="项目进度"><a href="#项目进度" class="headerlink" title="项目进度"></a>项目进度</h1><p>还在开发中，mac版已合入主分支，windows版还未合入主分支，但是也可以预览。</p><h1 id="生态"><a href="#生态" class="headerlink" title="生态"></a>生态</h1><p>目前生态还在完善，市面上还没有人用到。第三方包比不上web端的多，但基本能找到。比如行情图 <a href="https://pub.dev/packages/syncfusion_flutter_charts" target="_blank" rel="noopener">https://pub.dev/packages/syncfusion_flutter_charts</a></p><h1 id="体积大小"><a href="#体积大小" class="headerlink" title="体积大小"></a>体积大小</h1><p>空项目生成包大概60M，雪糕生成包67M，zip压缩后20M</p><h1 id="菜单栏设置"><a href="#菜单栏设置" class="headerlink" title="菜单栏设置"></a>菜单栏设置</h1><p><a href="https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar" target="_blank" rel="noopener">https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar</a></p><p>目前支持mac 导航栏设置、支持快捷键。</p><p>windows的还在开发中：</p><p><a href="https://github.com/google/flutter-desktop-embedding/issues/105" target="_blank" rel="noopener">https://github.com/google/flutter-desktop-embedding/issues/105</a></p><h1 id="系统通知"><a href="#系统通知" class="headerlink" title="系统通知"></a>系统通知</h1><p>暂时没有找到</p><h1 id="文件操作"><a href="#文件操作" class="headerlink" title="文件操作"></a>文件操作</h1><p>支持，需要权限</p><h1 id="应用更新"><a href="#应用更新" class="headerlink" title="应用更新"></a>应用更新</h1><p>官方没有提供，需自行设计</p><p><a href="https://github.com/google/flutter-desktop-embedding/issues/469" target="_blank" rel="noopener">https://github.com/google/flutter-desktop-embedding/issues/469</a></p>]]></content>
    
    <summary type="html">
    
      Flutter桌面端调研


官方文档

https://flutter.dev/desktop

背景
我们要做桌面版的客户端，有原生和RN两种选择。因为之前开发过flutter，记得flutter也支持桌面端，所以就试用了下。

flutter的介绍及开发见这篇文章

https://blog.dappwind.com/2019/09/01/index.html

打包好的Demo例子
雪糕应
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Flutter在雪球应用广场的实践</title>
    <link href="https://blog.dappwind.com/2019/09/01/"/>
    <id>https://blog.dappwind.com/2019/09/01/</id>
    <published>2019-09-01T10:27:47.000Z</published>
    <updated>2021-08-21T02:22:51.119Z</updated>
    
    <content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>雪球目前有3个App，雪球、蛋卷基金、雪盈证券，每个app又有各种版本比如feat、rc、sep等等。在开发阶段打出包后，如何能让开发同学测试同学，设计同学，产品经理等快速找到，并方便的安装到手机上？很旧之前是有个网页提供下载，但是年久失修，没有分类不支持搜索，安装也不太方便。所以就有了重构雪球应用广场的需求。</p><p>整个重构包括打包优化，后台接口，与前台界面。这里主要介绍前台界面。</p><p>19年7月，开始做这个app，大概一个月的时间，从入门学习到公司内部上线，完成3端开发，Android iOS web。也体现出了flutter的优势。</p><p>app样子如下:<br><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9baa5445a9a3~tplv-t2oaga2asx-image.image" alt></p><h1 id="Flutter"><a href="#Flutter" class="headerlink" title="Flutter"></a>Flutter</h1><p>最近flutter异常火热，Flutter是Google开源的跨端UI工具包，能一套代码提供ios android web三端的应用。我们基础研发平台也一直想在雪球内部试用下，当个快速试错者，帮助大家快速踩坑。正好借这个重构的契机，所以选取了flutter的跨端开发框架。结果证明确实很香，没让我们失望。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9bd2e52772eb~tplv-t2oaga2asx-image.image" alt></p><p>我们看这个图，除了手机端和web端之外，flutter也是支持电脑端应用和嵌入式应用的开发，除了想跟React Native PK 之外，还想跟桌面端开发框架electron及嵌入式C语言PK下。另外也是谷歌另一个新系统Fuchsia的主要开发方式。</p><h1 id="Flutter的优点"><a href="#Flutter的优点" class="headerlink" title="Flutter的优点"></a>Flutter的优点</h1><p>为什么这么多厂商使用flutter，根据我近段时间开发flutter的新得体会，列出几点。</p><h2 id="1-跨平台，渲染一致，一套代码，多端使用"><a href="#1-跨平台，渲染一致，一套代码，多端使用" class="headerlink" title="1.跨平台，渲染一致，一套代码，多端使用"></a>1.跨平台，渲染一致，一套代码，多端使用</h2><p>首先是他的跨平台真的做的很好，不同平台渲染一致性很高，真正做到了一套代码多端运行，而不是像之前的跨端方案 一套代码 多端修改适配。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9c2eba4b993c~tplv-t2oaga2asx-image.image" alt></p><p>这个图是雪糕应用在不同系统下的样子，UI几乎无差别，开发的时候不需要关注平台，android和ios的适配基本没花多长时间。</p><p>当时令我眼前一亮的一点是他竟然能把这种一致性延申到了web上。因为当时开发的时候，flutter还是1.7版本，官方还不支持web h5, 所以对能否做出web版没抱任何希望，当时想的是单独开发一套出h5版, 后来客户端开发完后，尝试性用内测分支flutter_web 适配了下，发现还是可以用的。UI体验与客户端差不多，除了有些bug与卡顿。所以既然web也能支持，我想他们之后说的桌面端和嵌入式也很期待。现在1.9 版本 web已经支持了，不用像我开发时，做大量的安装和适配工作了。</p><p>注：在今年（2020）我们桌面版选型时，我试用了下flutter桌面版的生成，只需升级下flutter版本，没有任何代码改动就打出了flutter桌面版，界面效果很优秀。文章如下链接。</p><blockquote><p><a href="https://blog.dappwind.com/2020/03/17">https://blog.dappwind.com/2020/03/17</a></p></blockquote><h2 id="2-开发效率高"><a href="#2-开发效率高" class="headerlink" title="2.开发效率高"></a>2.开发效率高</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9c95b5c00b7e~tplv-t2oaga2asx-image.image" alt></p><p>之前客户端同学一直羡慕我们前端同学开发效率高，代码改动后只要保存下，就能直接在界面上看到效果。 flutter也支持热重载这个功能。可以提高开发效率。</p><p>另外就是编辑器的代码补全，每个组件代码前面都有个一个电灯泡，可以直接选择添加哪些代码片段，并不是简单的代码很智能，减少了手动敲重复代码，提高开发效率。</p><h2 id="3-UI可深度定制，"><a href="#3-UI可深度定制，" class="headerlink" title="3.UI可深度定制，"></a>3.UI可深度定制，</h2><p>第三点就是UI可深度定制，性能媲美原生应用，可以通过下面这个视频看一下。</p><video id="video" controls preload="false"><br><source id="mp4" src="https://flutter.dev/videos/Filters.mp4" type="video/mp4"><br></video><h1 id="Flutter-的优点是怎么实现的"><a href="#Flutter-的优点是怎么实现的" class="headerlink" title="Flutter 的优点是怎么实现的"></a>Flutter 的优点是怎么实现的</h1><p>为什么会有这些优点，我们简单看一下flutter的框架，底层不调用原生组件，都是用skia引擎画出，所以能有很高的跨平台一致性，UI深度定制性，性能好。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9ce6c87b25d5~tplv-t2oaga2asx-image.image" alt></p><h2 id="Dart语言"><a href="#Dart语言" class="headerlink" title="Dart语言"></a>Dart语言</h2><p>而开发热重载则是Dart语言的一个优势。Dart支持两种模式</p><p><code>JIT</code> (Just-In-Time - 实时编译)  解释模式 </p><p><code>AOT</code> (Ahead-Of-Time - 预先编译)  编译模式</p><p>debug模式下使用的是解释模式，跟js一样，所以支持热重载，发布打包时，会先编译，性能比js好得多。所以也是flutter性能媲美原生的原因。</p><p>1.Dart 的性能更好。</p><p>Dart在 JIT模式下，速度与 JavaScript基本持平。但是 Dart支持 AOT，当以 AOT模式运行时，JavaScript便远远追不上了。速度的提升对高帧率下的视图数据计算很有帮助。</p><p>2.Native Binding。</p><p>在 Android上，v8的 Native Binding可以很好地实现，但是 iOS上的 JavaScriptCore不可以，所以如果使用 JavaScript，Flutter 基础框架的代码模式就很难统一了。而 Dart的 Native Binding可以很好地通过 Dart Lib实现。</p><p>3.Dart是类型安全的语言，拥有完善的包管理和诸多特性。</p><p>Google召集了如此多个编程语言界的设计专家开发出这样一门语言，旨在取代 JavaScript，所以 Fuchsia OS内置了 Dart。Dart可以作为 embedded lib嵌入应用，而不用只能随着系统升级才能获得更新，这也是优势之一。</p><p>4.Dart 2.5 支持了ML代码补全，调用C语言等功能</p><h2 id="Flutter项目结构"><a href="#Flutter项目结构" class="headerlink" title="Flutter项目结构"></a>Flutter项目结构</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9d55e4fcbd66~tplv-t2oaga2asx-image.image" alt></p><h1 id="Flutter编程思想"><a href="#Flutter编程思想" class="headerlink" title="Flutter编程思想"></a>Flutter编程思想</h1><p>一切皆 Widget<br>Widget 是 Flutter 应用用户界面的基本构建单元，每个 widget 都与最终的用户界面的展示紧密相关。不同于其他框架和平台 —— 将视图 (views)、视图控制器 (view controllers)、布局 (layouts) 等其他属性分开，Flutter 拥有统一的对象模型：widget。<br>一个 widget 可以定义：</p><ul><li>一个结构元素（比如一个按钮或者菜单）</li><li>一个风格元素（比如一个字体或者配色方案）</li><li>布局的一个方面（比如 padding）</li><li>等等……</li></ul><p>是声明式UI，一切都是widget,  一切都是小组件。<br>触摸操作也是组件。</p><p>下面是一个widget组件的例子，雪糕里有很多不同的app图标，大小，种类，有无边框会有些区别。所以封装成了组件，暴露参数 为 app内容，宽度，有无边框。</p><p>边框和背景图为最外层widget，里面有个文字的widegt.</p><p>这就是全部的icon组件代码。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9d7946119db7~tplv-t2oaga2asx-image.image" alt></p><h1 id="Flutter-插件-包管理"><a href="#Flutter-插件-包管理" class="headerlink" title="Flutter 插件/包管理"></a>Flutter 插件/包管理</h1><p>类似前端 npm , 我问了下客户端的同事，引用第三方的包时，需要直接上github上搜索，并手动下载。比较麻烦，这个pub.dev就采取了前端开发包管理的方式，是个单独的网站，直接搜索，然后添加到pubspec.yaml里面就行。会自动安装。</p><p>雪糕用到的插件如下：<br><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9d882c227db5~tplv-t2oaga2asx-image.image" alt></p><p>以分享插件为例<br><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9d94051341e5~tplv-t2oaga2asx-image.image" alt></p><h1 id="雪糕主要功能及其实现方式"><a href="#雪糕主要功能及其实现方式" class="headerlink" title="雪糕主要功能及其实现方式"></a>雪糕主要功能及其实现方式</h1><blockquote><p>1.顶部动画及导航栏  -  NestedScrollView / headerSliverBuilder / SliverPersistentHeaderDelegate 画出</p></blockquote><blockquote><p>2.网络请求及页面间数据通信 - http / JSON / event_bus</p></blockquote><blockquote><p>3.应用内下载及安装 - android: flutter_downloader &amp; path_provider /  iOS: url_launcher / web: dart open </p></blockquote><blockquote><p>4.搜索功能 - SearchDelegate</p></blockquote><blockquote><p>5.页面框架 - Scaffold / ListView</p></blockquote><blockquote><p>6.封装组件 - 列表组件 / 卡片组件 / icon组件</p></blockquote><blockquote><p>7.判断系统环境 - device_info</p></blockquote><p>这个地方罗列了一些主要的效果与功能，我们选取两个来具体说下，</p><h2 id="首先是顶部动画和导航栏"><a href="#首先是顶部动画和导航栏" class="headerlink" title="首先是顶部动画和导航栏"></a>首先是顶部动画和导航栏</h2><video id="video" controls preload="false"><br><source id="mp4" src="https://blogyu.oss-cn-beijing.aliyuncs.com/video/%E9%A1%B6%E9%83%A8480.mov" type="video/mp4"><br></video><p>我们的设计师设计了非常好看的顶部效果和导航栏样式，使用flutter自带的materal design的UI组件不能满足需求。导航栏背景是个可以动的图片背景，一直延伸到系统状态栏。并且顶部滑动效果，背景多个元素是差速移动的，另外，卡片是压在背景上方，上滑到某一位置后，卡片开始被收到状态栏背景下方。并且还要适配机型与屏幕。</p><p>由于刚开始用flutter, 看到设计稿时感觉flutter做不到这个效果，完全没有头绪，后来弄了好几天，换了3个方案，基本实现了这个动效</p><p>没有用flutter提供的appbar 相当于自己画了一个。因为flutter有stack层叠的概念，将差速移动的各个元素放在不同图层，每个的定位都加一个运动曲线公式。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9e07193c9a6f~tplv-t2oaga2asx-image.image" alt></p><p>代码大概结构如下 stack 元素  堆叠了 5个 positioned 图层</p><p>开发时还遇到了 listview滚动与appbanner 滚动事件冲突的问题，也是花费了些时间解决，又遇到类似问题的同学可以参考下git代码。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9e10ca38ed06~tplv-t2oaga2asx-image.image" alt></p><h2 id="另外介绍下雪糕内安装app的实现方式"><a href="#另外介绍下雪糕内安装app的实现方式" class="headerlink" title="另外介绍下雪糕内安装app的实现方式"></a>另外介绍下雪糕内安装app的实现方式</h2><p>安装app   ios 与 android很不一样，ios安装直接safari打开一个plist链接。所以用到了url_launcher 这个插件，他会打开链接进而进行安装。</p><p>安卓需要先下载再安装，安卓下载涉及到队列数据库等等设置，可以参考flutter_downloader 的说明文档。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9e21446b4195~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9e2eaa2868c1~tplv-t2oaga2asx-image.image" alt></p><h1 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h1><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/3/171d9e3560ed897e~tplv-t2oaga2asx-image.image" alt></p><h1 id="RN对比"><a href="#RN对比" class="headerlink" title="RN对比"></a>RN对比</h1><p>RN采用原生组件，与系统内的一致性强，但是ios跟android有些差异</p><p>第三方包活跃</p><p>RN list有些性能问题</p><h1 id="Flutter遇到的问题"><a href="#Flutter遇到的问题" class="headerlink" title="Flutter遇到的问题"></a>Flutter遇到的问题</h1><p>1.SliverAppBar 与 scroll controler 冲突</p><blockquote><p>SliverAppBar will not collapse when ListView is scrolled<br>来自 <a href="https://github.com/flutter/flutter/issues/26243" target="_blank" rel="noopener">https://github.com/flutter/flutter/issues/26243</a> </p></blockquote><blockquote><p><a href="https://stackoverflow.com/questions/46817189/sliverappbar-and-listview-with-controller/46853315#46853315" target="_blank" rel="noopener">https://stackoverflow.com/questions/46817189/sliverappbar-and-listview-with-controller/46853315#46853315</a></p></blockquote><p>Listview tabbarview 在 customscrollview 中<br>List 滚动不会引起 appbar滚动</p><h3 id="解决："><a href="#解决：" class="headerlink" title="解决："></a>解决：</h3><p>NestedScrollView 中的 headerSliverBuilder</p><p>NestedScrollView 不只可以用 <code>SliverAppBar</code> 还可以接受 <code>SliverPersistentHeader</code></p><p>而 <code>SliverPersistentHeader</code> 中可处理滑动，结合<code>NotificationListener</code>可实现滚动效果。</p><p>2.Future 异步 需在页面切换走后 停止，否则setState报错</p><blockquote><p><a href="https://stackoverflow.com/questions/49340116/setstate-called-after-dispose" target="_blank" rel="noopener">https://stackoverflow.com/questions/49340116/setstate-called-after-dispose</a><br><figure class="highlight dart"><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="keyword">if</span> (<span class="keyword">this</span>.mounted)&#123;</span><br><span class="line"> setState(()&#123;</span><br><span class="line">  <span class="comment">//Your state change code goes here</span></span><br><span class="line"> &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p></blockquote><p>3.Text hero </p><p>颜色会变红，字体会变，会换行</p><blockquote><p><a href="https://github.com/flutter/flutter/issues/10246" target="_blank" rel="noopener">https://github.com/flutter/flutter/issues/10246</a><br><a href="https://github.com/flutter/flutter/issues/30647" target="_blank" rel="noopener">https://github.com/flutter/flutter/issues/30647</a></p></blockquote><p>Text hero<br>设置颜色、设置字体、设置宽度，依旧效果不太好。</p>]]></content>
    
    <summary type="html">
    
      背景
雪球目前有3个App，雪球、蛋卷基金、雪盈证券，每个app又有各种版本比如feat、rc、sep等等。在开发阶段打出包后，如何能让开发同学测试同学，设计同学，产品经理等快速找到，并方便的安装到手机上？很旧之前是有个网页提供下载，但是年久失修，没有分类不支持搜索，安装也不太方便。所以就有了重构雪球应用广场的需求。

整个重构包括打包优化，后台接口，与前台界面。这里主要介绍前台界面。

19年7
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>ES APM 监控报警</title>
    <link href="https://blog.dappwind.com/2019/06/24/"/>
    <id>https://blog.dappwind.com/2019/06/24/</id>
    <published>2019-06-24T07:51:40.000Z</published>
    <updated>2021-08-21T02:22:51.119Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一-背景"><a href="#一-背景" class="headerlink" title="一.背景"></a>一.背景</h2><p>日志数据和apm数据被es处理后，能配置出可视化的查询图表，能及时了解server和网站的实时运行情况，但是还是需要人工定时查看。所以对es采集处理后的分类数据和图表，实现自动监控和报警的功能</p><h2 id="二-方案"><a href="#二-方案" class="headerlink" title="二.方案"></a>二.方案</h2><h3 id="1-es-自己的watcher-alert-功能"><a href="#1-es-自己的watcher-alert-功能" class="headerlink" title="1.es 自己的watcher alert 功能"></a>1.es 自己的watcher alert 功能</h3><p>es 的 basic 授权license不带 watcher功能，需要购买 GOLD license</p><blockquote><p><a href="https://www.elastic.co/subscriptions" target="_blank" rel="noopener">https://www.elastic.co/subscriptions</a></p></blockquote><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/24/16b896c8806ca587~tplv-t2oaga2asx-image.image" alt></p><h3 id="2-单独的开源alert服务"><a href="#2-单独的开源alert服务" class="headerlink" title="2.单独的开源alert服务"></a>2.单独的开源alert服务</h3><p><a href="https://github.com/daaru00/es-alert" target="_blank" rel="noopener">https://github.com/daaru00/es-alert</a></p><p><a href="https://github.com/Akagi201/esalert" target="_blank" rel="noopener">https://github.com/Akagi201/esalert</a></p><p>缺点：无可视化配置界面，用的人不多</p><h3 id="3-采用grafana-alert"><a href="#3-采用grafana-alert" class="headerlink" title="3.采用grafana alert"></a>3.采用grafana alert</h3><p>grafana 自带一个go的server服务，可实现可视化报警配置，可以很方便的配置成钉钉群报警机器人。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/24/16b896cc8f491ab1~tplv-t2oaga2asx-image.image" alt></p><h2 id="三-配置"><a href="#三-配置" class="headerlink" title="三.配置"></a>三.配置</h2><h3 id="1-grafana-添加es-为database"><a href="#1-grafana-添加es-为database" class="headerlink" title="1.grafana 添加es 为database"></a>1.grafana 添加es 为database</h3><p>index name 跟kibana的index一样即可</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/24/16b896df9b88fe13~tplv-t2oaga2asx-image.image" alt></p><p>点击测试，提示成功。</p><h3 id="2-配置查询语句，生成图表"><a href="#2-配置查询语句，生成图表" class="headerlink" title="2.配置查询语句，生成图表"></a>2.配置查询语句，生成图表</h3><p>查询语句可以在kibana中测试，没问题后再复制到grafana,grafana的数据展示还支持函数处理，感觉比kibana强大方便些，而且更快。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/24/16b896ec6a3aa6bc~tplv-t2oaga2asx-image.image" alt></p><h3 id="3-配置报警"><a href="#3-配置报警" class="headerlink" title="3.配置报警"></a>3.配置报警</h3><p>每个图表可以配置单独的报警，选择方便的钉钉机器人即可。</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/24/16b896ef9bb1fa5e~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/24/16b896f22011551d~tplv-t2oaga2asx-image.image" alt></p>]]></content>
    
    <summary type="html">
    
      一.背景
日志数据和apm数据被es处理后，能配置出可视化的查询图表，能及时了解server和网站的实时运行情况，但是还是需要人工定时查看。所以对es采集处理后的分类数据和图表，实现自动监控和报警的功能

二.方案
1.es 自己的watcher alert 功能
es 的 basic 授权license不带 watcher功能，需要购买 GOLD license

https://www.ela
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Gulp老项目升级为webpack打包及相关优化</title>
    <link href="https://blog.dappwind.com/2019/05/25/"/>
    <id>https://blog.dappwind.com/2019/05/25/</id>
    <published>2019-05-25T07:26:33.000Z</published>
    <updated>2021-08-21T02:22:51.119Z</updated>
    
    <content type="html"><![CDATA[<h1 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h1><ol><li><p>vue 和 js 改为webpack打包 </p><ul><li>升级babel</li><li>引入webpack config</li><li>添加webpack loader</li><li>生成入口列表 entry</li><li>结合到gulp流程</li><li>增加可视化打包分析</li></ul></li><li><p>开发模式热更新</p><ul><li>webpack-dev-server</li></ul></li><li><p>vue 拆分多入口 实现按需加载 按需引用vue-component</p><ul><li>vue 文件入口 为指定入口 </li><li>vue-web</li><li>vue-mobile</li><li>vue-article</li><li>vue-home</li><li>vue-stock </li></ul></li><li><p>vue 公共资源抽离vue-common</p><ul><li>增加splitChunks</li><li>chunks 设置 只抽取vue部分</li></ul></li><li><p>js 引用方式修改为 从vue引入</p><ul><li>减少体积</li><li>不用手动控制js加载</li></ul></li><li><p>拆分vue路由</p></li><li><p>lodash 按需引用 去除全局lodash</p><ul><li>减小体积</li><li>避免冲突</li></ul></li><li><p>js资源 preload</p><ul><li>加快速度</li></ul></li><li><p>vue warning 解决</p><ul><li>之前的打包方式 不会有warning提示</li></ul></li><li>confirm modal 拆分（暂缓）<ul><li>新增的考虑拆分</li></ul></li><li>poliyfill<ul><li>单独引用 30k</li></ul></li></ol><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>snowman项目采用<code>pug</code>渲染 + <code>vue</code>渲染结合，vue渲染部分js资源未按需加载，页面加载全部的js</p><h1 id="一-vue-和-js-改为webpack打包"><a href="#一-vue-和-js-改为webpack打包" class="headerlink" title="一. vue 和 js 改为webpack打包"></a>一. vue 和 js 改为webpack打包</h1><h2 id="1-升级babel"><a href="#1-升级babel" class="headerlink" title="1.升级babel"></a>1.升级babel</h2><figure class="highlight"><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></pre></td><td class="code"><pre><span class="line">"devDependencies": &#123;</span><br><span class="line">    "@babel/cli": "^7.4.4",</span><br><span class="line">    "@babel/core": "^7.4.4",</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>.babelrc 改用preset-env<br><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"presets"</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">"targets"</span>: &#123;</span><br><span class="line">          <span class="attr">"browsers"</span>: [</span><br><span class="line">            <span class="string">"Android &gt;= 4.0"</span>,</span><br><span class="line">            <span class="string">"ios &gt;= 9"</span>,</span><br><span class="line">            <span class="string">"ie 9"</span></span><br><span class="line">          ]</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">"plugins"</span>: [</span><br><span class="line">    <span class="string">"@babel/plugin-syntax-dynamic-import"</span>,</span><br><span class="line">    <span class="string">"@babel/plugin-transform-runtime"</span>,</span><br><span class="line">    [<span class="string">"import"</span>, &#123;</span><br><span class="line">      <span class="attr">"libraryName"</span>: <span class="string">"lodash"</span>,</span><br><span class="line">      <span class="attr">"libraryDirectory"</span>: <span class="string">""</span>,</span><br><span class="line">      <span class="attr">"camel2DashComponentName"</span>: <span class="literal">false</span>,</span><br><span class="line">    &#125;]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>node入口babel升级</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">'@babel/register'</span>)(&#123;</span><br><span class="line">  presets: [<span class="string">'@babel/preset-env'</span>]</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">'@babel/polyfill'</span>);</span><br></pre></td></tr></table></figure><h2 id="2-引入webpack-config"><a href="#2-引入webpack-config" class="headerlink" title="2.引入webpack config"></a>2.引入webpack config</h2><p><code>webpack.common.js</code></p><p><code>webpack.dev.js</code></p><p><code>webpack.prod.js</code></p><h4 id="entry-打包入口"><a href="#entry-打包入口" class="headerlink" title="entry 打包入口"></a>entry 打包入口</h4><figure class="highlight plain"><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">&apos;vue-web&apos;: &apos;./src/vue/web.js&apos;,</span><br><span class="line">&apos;vue-mobile&apos;: &apos;./src/vue/mobile.js&apos;,</span><br></pre></td></tr></table></figure><h2 id="3-添加webpack-loader"><a href="#3-添加webpack-loader" class="headerlink" title="3.添加webpack loader"></a>3.添加webpack loader</h2><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">rules: [</span><br><span class="line">    &#123;</span><br><span class="line">      test: <span class="regexp">/\.vue$/</span>,</span><br><span class="line">      loader: <span class="string">'vue-loader'</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">// this applies to &lt;template lang="pug"&gt; in Vue components</span></span><br><span class="line">      test: <span class="regexp">/\.pug$/</span>,</span><br><span class="line">      oneOf: [</span><br><span class="line">        <span class="comment">// 这条规则应用到 Vue 组件内的 `&lt;template lang="pug"&gt;`</span></span><br><span class="line">        &#123;</span><br><span class="line">          resourceQuery: <span class="regexp">/^\?vue/</span>,</span><br><span class="line">          use: [<span class="string">'pug-plain-loader'</span>]</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment">// 这条规则应用到 JavaScript 内的 pug 导入</span></span><br><span class="line">        &#123;</span><br><span class="line">          use: [<span class="string">'pug-loader'</span>]</span><br><span class="line">        &#125;</span><br><span class="line">      ]</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// &#123;</span></span><br><span class="line">    <span class="comment">//   test: /\.(css|less)$/,</span></span><br><span class="line">    <span class="comment">//   use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']</span></span><br><span class="line">    <span class="comment">// &#125;,</span></span><br><span class="line">    &#123;</span><br><span class="line">      test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">      exclude: <span class="regexp">/(node_modules|bower_components)/</span>,</span><br><span class="line">      <span class="comment">// include: /src/,</span></span><br><span class="line">      loader: <span class="string">'babel-loader?cacheDirectory=true'</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h2 id="4-增加可视化打包分析"><a href="#4-增加可视化打包分析" class="headerlink" title="4.增加可视化打包分析"></a>4.增加可视化打包分析</h2><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">if</span> (process.env.VISU) &#123;</span><br><span class="line">  common.plugins.push(<span class="keyword">new</span> BundleAnalyzerPlugin(&#123;</span><br><span class="line">    analyzerHost: <span class="string">'localhost'</span>,</span><br><span class="line">    analyzerPort: <span class="string">'8080'</span></span><br><span class="line">  &#125;));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用<code>npm run visu</code> 即可浏览</p><p>分析最开始的vue-web.js 为500k </p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aeddba09f491b9~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aeddbbeaac50ea~tplv-t2oaga2asx-image.image" alt></p><p>发现是snbchart和moment-timezone 错误引用造成的</p><p>查找问题发现</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aeddc94ec178ca~tplv-t2oaga2asx-image.image" alt></p><p>取消错误引用后，减小到350k</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aeddcf98c255d2~tplv-t2oaga2asx-image.image" alt></p><h2 id="5-生成入口列表-js-entry"><a href="#5-生成入口列表-js-entry" class="headerlink" title="5.生成入口列表 js entry"></a>5.生成入口列表 js entry</h2><p>目前只有vue文件是webpack 打包的，snowman是多入口 还有很多js文件也要分别打包处理</p><p>遍历src/js 获得webpack 入口列表</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> getEntrys = <span class="function">(<span class="params">path</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> entry = &#123;&#125;;</span><br><span class="line">  <span class="keyword">var</span> files = glob.sync(path);</span><br><span class="line">  files.forEach(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 获取js名称</span></span><br><span class="line">    <span class="keyword">const</span> name = item.match(<span class="regexp">/.*\/(.*?).js$/</span>)[<span class="number">1</span>];</span><br><span class="line">    <span class="keyword">if</span> (name) &#123;</span><br><span class="line">      entry[name] = item;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> entry;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> jsEntry = getEntrys(<span class="string">'./src/js/*.js'</span>);</span><br></pre></td></tr></table></figure><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">const</span> entry = &#123;</span><br><span class="line">  <span class="string">'vue-web'</span>: <span class="string">'./src/vue/web.js'</span>,</span><br><span class="line">  <span class="string">'vue-mobile'</span>: <span class="string">'./src/vue/mobile.js'</span>,</span><br><span class="line">  ...jsEntry,</span><br><span class="line">  <span class="string">'im'</span>: <span class="string">'./src/js/im/index.js'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>至此webpack打包已经能生成各种js文件，但是还不能运行。所以需要结合到gulp流程</p><h2 id="6-结合到gulp流程"><a href="#6-结合到gulp流程" class="headerlink" title="6.结合到gulp流程"></a>6.结合到gulp流程</h2><p>我们的pug文件热更新需要沿用gulp流程</p><p>webpack output至 gulp dist/js 目录</p><p>生成的文件注意不要加hash， gulp有统一处理</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></pre></td><td class="code"><pre><span class="line">output: &#123;</span><br><span class="line">  filename: <span class="string">'[name].js'</span>,</span><br><span class="line">  path: path.resolve(__dirname, <span class="string">'dist/js'</span>),</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>增加webpack gulp命令</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> webpack <span class="keyword">from</span> <span class="string">'webpack'</span>;</span><br><span class="line"><span class="comment">// 载入webpack.config.js文件</span></span><br><span class="line"><span class="keyword">import</span> productConfig <span class="keyword">from</span> <span class="string">'../webpack.prod'</span>;</span><br><span class="line"><span class="keyword">import</span> devConfig <span class="keyword">from</span> <span class="string">'../webpack.dev'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> isDev = process.env.NODE_ENV === <span class="string">'development'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</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</span>) =&gt;</span> &#123;</span><br><span class="line">    webpack(isDev ? devConfig : productConfig, <span class="function"><span class="keyword">function</span> (<span class="params">err, stats</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="built_in">console</span>.err(err);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        resolve();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改gulpfile</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aede106e581a52~tplv-t2oaga2asx-image.image" alt></p><h1 id="二-开发模式热更新"><a href="#二-开发模式热更新" class="headerlink" title="二. 开发模式热更新"></a>二. 开发模式热更新</h1><p>采用 webpack-dev-server</p><p>启动在5001端口</p><p>为了与gulp dev 模式兼容， writeToDist 为true ，同样写到gulp 目录</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> merge = <span class="built_in">require</span>(<span class="string">'webpack-merge'</span>);</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line"><span class="keyword">const</span> CopyWebpackPlugin = <span class="built_in">require</span>(<span class="string">'copy-webpack-plugin'</span>);</span><br><span class="line"><span class="keyword">const</span> common = <span class="built_in">require</span>(<span class="string">'./webpack.common.js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = merge(common, &#123;</span><br><span class="line">  mode: <span class="string">'development'</span>,</span><br><span class="line">  <span class="comment">// devtool: 'inline-source-map',</span></span><br><span class="line">  devServer: &#123;</span><br><span class="line">    contentBase: <span class="string">'./dist/js'</span>,</span><br><span class="line">    hot: <span class="literal">true</span>,</span><br><span class="line">    writeToDisk: <span class="literal">true</span>,</span><br><span class="line">    headers: &#123;</span><br><span class="line">      <span class="string">'Access-Control-Allow-Origin'</span>: <span class="string">'*'</span></span><br><span class="line">    &#125;,</span><br><span class="line">    host: <span class="string">'0.0.0.0'</span>,</span><br><span class="line">    port: <span class="number">5001</span></span><br><span class="line">  &#125;,</span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="keyword">new</span> webpack.HotModuleReplacementPlugin(),</span><br><span class="line">    <span class="keyword">new</span> webpack.DefinePlugin(&#123;</span><br><span class="line">      <span class="string">'process.env.NODE_ENV'</span>: <span class="built_in">JSON</span>.stringify(<span class="string">'development'</span>)</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> CopyWebpackPlugin([&#123; <span class="attr">from</span>: <span class="string">'./src/js/lib'</span>, <span class="attr">to</span>: <span class="string">'./'</span> &#125;])</span><br><span class="line">  ]</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>output publicPath 设置为0.0.0.0:5001 , 目的是为了热更新的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></pre></td><td class="code"><pre><span class="line">output: &#123;</span><br><span class="line">    filename: <span class="string">'[name].js'</span>,</span><br><span class="line">    path: path.resolve(__dirname, <span class="string">'dist/js'</span>),</span><br><span class="line">    publicPath: <span class="string">'http://0.0.0.0:5001/'</span></span><br><span class="line">  &#125;,</span><br></pre></td></tr></table></figure><p>npm script </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;dev&quot;: &quot;npm run gulp-dev &amp; npm run webpack-dev-server&quot;,</span><br></pre></td></tr></table></figure><p> 编译界面</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee08ae4f01c2c~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee08e9d707c72~tplv-t2oaga2asx-image.image" alt></p><h1 id="三-vue-拆分多入口-实现按需加载"><a href="#三-vue-拆分多入口-实现按需加载" class="headerlink" title="三. vue 拆分多入口 实现按需加载"></a>三. vue 拆分多入口 实现按需加载</h1><p>目前都在一块，没有分页面分路由拆分   所有vue页面都有全部的vue-web.js</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee09c8897f495~tplv-t2oaga2asx-image.image" alt></p><p>原因在于只写了一个vue入口 src/vue/web.js  所有的vue组件都在这里引用，其实应该拆开</p><p>看注释 能看到大概的组件属于哪一页</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0a12558ea9d~tplv-t2oaga2asx-image.image" alt></p><p>计划先按照主要页面 拆分 </p><ul><li>文章页</li><li>个股页</li><li>已登录首页</li><li>未登录首页</li><li>编辑器页</li></ul><p>抽取通用的vue.common.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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> entry = &#123;</span><br><span class="line">  <span class="string">'vue-web'</span>: <span class="string">'./src/vue/web.js'</span>,</span><br><span class="line">  <span class="string">'vue-mobile'</span>: <span class="string">'./src/vue/mobile.js'</span>,</span><br><span class="line">  <span class="string">'vue-article'</span>: <span class="string">'./src/vue/article.js'</span>,</span><br><span class="line">  <span class="string">'vue-stock'</span>: <span class="string">'./src/vue/stock.js'</span>,</span><br><span class="line">  <span class="string">'vue-home'</span>: <span class="string">'./src/vue/home.js'</span>,</span><br><span class="line">  <span class="string">'vue-home-unsign'</span>: <span class="string">'./src/vue/home-unsign.js'</span>,</span><br><span class="line">  <span class="string">'vue-write'</span>: <span class="string">'./src/vue/write.js'</span>,</span><br><span class="line">  ...jsEntry,</span><br><span class="line">  <span class="string">'im'</span>: <span class="string">'./src/js/im/index.js'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0aa8041d0de~tplv-t2oaga2asx-image.image" alt></p><p>后续考虑 使用dll将通用的node_modules 打包到一块, 加快打包速度，减少发版后用户端的js更新量。</p><h2 id="文章页"><a href="#文章页" class="headerlink" title="文章页"></a>文章页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0ae96790675~tplv-t2oaga2asx-image.image" alt></p><p>pug文件中引用</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0b17be032c7~tplv-t2oaga2asx-image.image" alt></p><h2 id="个股页"><a href="#个股页" class="headerlink" title="个股页"></a>个股页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0b380a0708e~tplv-t2oaga2asx-image.image" alt></p><p>pug文件中应用</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0b8557a6d5c~tplv-t2oaga2asx-image.image" alt></p><h2 id="已登录首页"><a href="#已登录首页" class="headerlink" title="已登录首页"></a>已登录首页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0bc6769e59a~tplv-t2oaga2asx-image.image" alt></p><h2 id="未登录首页"><a href="#未登录首页" class="headerlink" title="未登录首页"></a>未登录首页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0c0f46fbdf0~tplv-t2oaga2asx-image.image" alt></p><h1 id="四-抽离公共vue-common"><a href="#四-抽离公共vue-common" class="headerlink" title="四. 抽离公共vue-common"></a>四. 抽离公共vue-common</h1><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0c8f98e8208~tplv-t2oaga2asx-image.image" alt></p><p>每个js有很多重复的部分</p><p>vue 公共资源抽离vue-common</p><p>增加splitChunks</p><blockquote><p><a href="https://webpack.js.org/plugins/split-chunks-plugin/" target="_blank" rel="noopener">https://webpack.js.org/plugins/split-chunks-plugin/</a></p></blockquote><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0d2782017cc~tplv-t2oaga2asx-image.image" alt></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></pre></td><td class="code"><pre><span class="line">optimization: &#123;</span><br><span class="line">    splitChunks: &#123;</span><br><span class="line">      <span class="comment">// 只抽取vue开头的公共部分  为了向下兼容 不抽取 vue-web vue-mobile</span></span><br><span class="line">      chunks (chunk) &#123;</span><br><span class="line">        <span class="keyword">return</span> chunk.name.match(<span class="regexp">/^vue-(?!(mobile|web))/</span>);</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="comment">// 取消自动抽取</span></span><br><span class="line">      minSize: <span class="number">3000000</span>,</span><br><span class="line">      maxAsyncRequests: <span class="number">5</span>,</span><br><span class="line">      maxInitialRequests: <span class="number">3</span>,</span><br><span class="line">      automaticNameDelimiter: <span class="string">'~'</span>,</span><br><span class="line">      name: <span class="literal">true</span>,</span><br><span class="line">      cacheGroups: &#123;</span><br><span class="line">        <span class="keyword">default</span>: &#123;</span><br><span class="line">          minSize: <span class="number">0</span>,</span><br><span class="line">          minChunks: <span class="number">4</span>,</span><br><span class="line">          priority: <span class="number">-10</span>,</span><br><span class="line">          name: <span class="string">'vue-common'</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee0d570378598~tplv-t2oaga2asx-image.image" alt></p><h1 id="五-js引用方式改为从vue引入"><a href="#五-js引用方式改为从vue引入" class="headerlink" title="五. js引用方式改为从vue引入"></a>五. js引用方式改为从vue引入</h1><p>之前的引用方式</p><ul><li>文章页</li></ul><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee110d3a8bbb1~tplv-t2oaga2asx-image.image" alt></p><ul><li>个股页</li></ul><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1139ddcba80~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee114f6c267e4~tplv-t2oaga2asx-image.image" alt></p><p>之前为了按需加载写的代码 可以不用单独在pug入口文件中写了，直接在 vue/article.js  或者 vue/stock.js 中 引用即可</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee11a7bf8bca7~tplv-t2oaga2asx-image.image" alt></p><p>改变引用方式后的打包图，可以发现 vue-stock 包含了 snbchart 并且 体积小于 之前stock_new.js的体积</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee11c51e08992~tplv-t2oaga2asx-image.image" alt></p><h1 id="六-拆分vue路由"><a href="#六-拆分vue路由" class="headerlink" title="六. 拆分vue路由"></a>六. 拆分vue路由</h1><p>看下面这个打包图，vue-common中包含了所有的路由，并且发现有个bussiness.js 的模块挺大的，后来发现这个模块只有个股页用到了，所以应该打包到vue-stock 这个文件里</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee13904d5f455~tplv-t2oaga2asx-image.image" alt></p><p>分析后发现是vue路由没有拆开的原因</p><p>vue/router/index </p><p>旧代码</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">let</span> routes;</span><br><span class="line"><span class="keyword">const</span> pathname = <span class="built_in">window</span>.location.pathname.replace(<span class="regexp">/^\/snowman/</span>, <span class="string">''</span>);</span><br><span class="line"><span class="keyword">if</span> (~<span class="string">'/'</span>.indexOf(pathname)) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">window</span>.SNOWMAN_LOGIN) &#123;</span><br><span class="line">    routes = <span class="built_in">require</span>(<span class="string">'./home'</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/^\/today/</span>.test(pathname)) &#123;</span><br><span class="line">  routes = <span class="built_in">require</span>(<span class="string">'./home'</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/^\/u\/\d+\/?$/</span>.test(pathname)) &#123;</span><br><span class="line">  routes = <span class="built_in">require</span>(<span class="string">'./profiles'</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/^\/k\/?$/</span>.test(pathname)) &#123;</span><br><span class="line">  routes = <span class="built_in">require</span>(<span class="string">'./search'</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/^\/u\/[A-z0-9_]&#123;4,&#125;\/?$/</span>.test(pathname)) &#123;</span><br><span class="line">  routes = <span class="built_in">require</span>(<span class="string">'./profiles'</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/^\/center\//</span>.test(pathname)) &#123;</span><br><span class="line">  routes = <span class="built_in">require</span>(<span class="string">'./center'</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/^\/[sS]\/([^\/]+)\/detail/</span>.test(pathname)) &#123;</span><br><span class="line">  routes = <span class="built_in">require</span>(<span class="string">'./stock-info'</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> routes;</span><br></pre></td></tr></table></figure><p>看这种写法是想拆分路由的 只是因为入口一样 无法拆分</p><p>现在有了不同的入口 就可以拆分了</p><p>改为 </p><p>只有个股页 才引用 stock-info</p><p>匿名首页 只引用 home</p><p>个人页 只引用 profiles</p><p>个人中心 只引用 center</p><p>搜索 只引用 search</p><p>效果如下</p><p>vue-common 252 -&gt; 200</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee18aa063dfbc~tplv-t2oaga2asx-image.image" alt></p><p>可以看到 router 从 vue-common 中消失了 </p><p>commponent 大小也减小，拆分到了 需要的js 中  实现按需加载 </p><p>common  中的 business 也正确的只打包在 vue-stock 中 </p><p>匿名首页 拆分 vue 路由后</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee19152b46692~tplv-t2oaga2asx-image.image" alt></p><h1 id="七-lodash按需引用-去除全局lodash"><a href="#七-lodash按需引用-去除全局lodash" class="headerlink" title="七. lodash按需引用 去除全局lodash"></a>七. lodash按需引用 去除全局lodash</h1><p>lodash 多个地方重复引用，并且是全量引用</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1a1dee6ada1~tplv-t2oaga2asx-image.image" alt></p><p>增加lodash tree shake</p><blockquote><p><a href="https://www.azavea.com/blog/2019/03/07/lessons-on-tree-shaking-lodash/" target="_blank" rel="noopener">https://www.azavea.com/blog/2019/03/07/lessons-on-tree-shaking-lodash/</a></p></blockquote><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1ac0526b38b~tplv-t2oaga2asx-image.image" alt></p><p><code>import foo from &#39;lodash/foo&#39;</code></p><p>以这种方式引用才可以tree shake</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1b2c6873e2e~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1b4477dd08b~tplv-t2oaga2asx-image.image" alt></p><p>从26k降到了6k</p><h2 id="使用-babel-plugin-import"><a href="#使用-babel-plugin-import" class="headerlink" title="使用 babel-plugin-import"></a>使用 babel-plugin-import</h2><p>但是每次都手动写，不太方便。</p><p>分析原理 <code>import { debounce } from &#39;lodash&#39;</code>这种引用方式不能按需加载的原因为：</p><p>其被babel转换为了<br><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">var</span> lodash = <span class="built_in">require</span>(<span class="string">'lodash'</span>);</span><br><span class="line"><span class="keyword">var</span> debounce = lodash.debounce;</span><br></pre></td></tr></table></figure></p><p>第一句 <code>var lodash = require(&#39;lodash&#39;);</code> 就把所有的lodash都引进来了。</p><p>所以我们需要一个插件，能正确的babel转换 </p><p><a href="https://github.com/ant-design/babel-plugin-import" target="_blank" rel="noopener"><code>babel-plugin-import</code></a> 就是提供了这么一个功能。</p><p>能直接转换为</p><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="keyword">var</span> debounce = <span class="built_in">require</span>(<span class="string">'lodash/debounce'</span>);</span><br></pre></td></tr></table></figure><p>所以最后我们采用 <code>babel-plugin-import</code>。并增加了lodash的引用路径配置。</p><p>采用<code>import { debounce } from &#39;lodash&#39;</code>这种写法即可实现按需加载。</p><p>并且引用其他库时，也可以配置按需引用了，比如我们的 <code>snb-lib-jsbridge</code>等。</p><h1 id="八-资源preload"><a href="#八-资源preload" class="headerlink" title="八. 资源preload"></a>八. 资源preload</h1><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1be43b52531~tplv-t2oaga2asx-image.image" alt></p><p>js资源加载时间很靠后</p><p>增加link preload</p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1c379428566~tplv-t2oaga2asx-image.image" alt></p><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1c456192c88~tplv-t2oaga2asx-image.image" alt></p><h1 id="九-目前资源加载情况"><a href="#九-目前资源加载情况" class="headerlink" title="九. 目前资源加载情况"></a>九. 目前资源加载情况</h1><h2 id="匿名首页"><a href="#匿名首页" class="headerlink" title="匿名首页"></a>匿名首页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1cda7bd72cb~tplv-t2oaga2asx-image.image" alt></p><h2 id="文章页-1"><a href="#文章页-1" class="headerlink" title="文章页"></a>文章页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1d3d3192af7~tplv-t2oaga2asx-image.image" alt></p><h2 id="个股页-1"><a href="#个股页-1" class="headerlink" title="个股页"></a>个股页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1d5a4b310b9~tplv-t2oaga2asx-image.image" alt></p><h2 id="已登录首页-1"><a href="#已登录首页-1" class="headerlink" title="已登录首页"></a>已登录首页</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1d6fef8a465~tplv-t2oaga2asx-image.image" alt></p><h2 id="包分析"><a href="#包分析" class="headerlink" title="包分析"></a>包分析</h2><p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/5/25/16aee1da5d88f986~tplv-t2oaga2asx-image.image" alt></p>]]></content>
    
    <summary type="html">
    
      概览
 1.  vue 和 js 改为webpack打包 
     
      * 升级babel
      * 引入webpack config
      * 添加webpack loader
      * 生成入口列表 entry
      * 结合到gulp流程
      * 增加可视化打包分析
     
     
 2.  开发模式热更新
     
      * we
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>ES APM字段检索问题解决</title>
    <link href="https://blog.dappwind.com/2019/05/21/"/>
    <id>https://blog.dappwind.com/2019/05/21/</id>
    <published>2019-05-21T12:58:49.000Z</published>
    <updated>2020-04-06T12:00:08.744Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://xqimg.imedao.com/16ada7ff81ce42b3fea7e62a.jpg" alt></p><p>使用apm时，处理数据时发现 有的字段没法检索，发现是没有建立mapping的原因，可以看到有个小三角，<br>一般这种情况下， 在 management &gt; index Patterns 中点击刷新就行</p><p><img src="https://xqimg.imedao.com/16ada82f567e32c3fac82e30.jpg" alt></p><p>但是跟预期不同的是，fields数量并没有增加，还是建立不了mapping.</p><p><img src="https://xqimg.imedao.com/16ada7ff83ae4553fcd9cd0c.jpg" alt></p><p>尝试修改mapping文件 发现只有通过api的方式修改，只能全量更新，而且点击refresh field list 后，改动又会变回去。所以放弃这种方法。</p><p>搜索网络 发现一个相似的问题</p><blockquote><p><a href="https://discuss.elastic.co/t/no-cached-mapping-for-this-field-apm/151196" target="_blank" rel="noopener">https://discuss.elastic.co/t/no-cached-mapping-for-this-field-apm/151196</a></p></blockquote><p>I am using APM to monitor my node.js application. In Kibana, I see some fields with the error : “ no cached mapping for this field”. As a result, I cannot run any analytics or visualization on these fields.</p><p>文章中提到的一个方法是设置fields.yml</p><p>fields.yml 在apm-server文档上只出现一次，说是自动生成的</p><blockquote><p><a href="https://github.com/elastic/apm-server#update" target="_blank" rel="noopener">https://github.com/elastic/apm-server#update</a></p></blockquote><p>Update</p><p>Each beat has a template for the mapping in elasticsearch and a documentation for the fields which is automatically generated based on fields.yml. To generate required configuration files and templates run:<br>make index-template update</p><p>查看已经生成的fields.yml</p><p>cd /etc/apm-server  </p><p><img src="https://xqimg.imedao.com/16ada7ff80be4293fc34ba0d.jpg" alt></p><p>可以看到这个配置文件跟kibana中的mapping 很像，修改这个应该可以实现修改mapping的目的</p><p><img src="https://xqimg.imedao.com/16ada80ecace5ec3fe2b8873.jpg" alt></p><p>其中http中 的设置在这里，可以看到dynamic为false  所以也解释了为什么怎么刷新fields都不会增加</p><p><img src="https://xqimg.imedao.com/16ada7ff817e42a3feed8b7c.jpg" alt></p><p>编辑/etc/apm-server 文件夹下的 fields.yml<br>修改相应字段设置 常用的加上 dynamic: true</p><p>然后修改 apm-server.yml 中的template配置</p><blockquote><p><a href="https://github.com/elastic/apm-server/blob/master/apm-server.yml#L180-L214" target="_blank" rel="noopener">https://github.com/elastic/apm-server/blob/master/apm-server.yml#L180-L214</a></p></blockquote><figure class="highlight plain"><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">setup.template.fields: &quot;$&#123;path.config&#125;/fields.yml&quot;</span><br><span class="line">setup.template.overwrite: false</span><br></pre></td></tr></table></figure><p>设置文档</p><blockquote><p><a href="https://www.elastic.co/guide/en/apm/server/master/configuration-template.html" target="_blank" rel="noopener">https://www.elastic.co/guide/en/apm/server/master/configuration-template.html</a></p></blockquote><blockquote><p><a href="https://github.com/elastic/beats/issues/8607" target="_blank" rel="noopener">https://github.com/elastic/beats/issues/8607</a></p></blockquote><p>随后重启apm-server </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart apm-server</span><br></pre></td></tr></table></figure><p>进入kibana 查看es的mapping 发现没有更新<br>可以删除当天的index，或者等第二天新建index时<br>然后新的index就发现已经更新了</p><p><img src="https://xqimg.imedao.com/16ada7ff906e4b63f9ece26a.jpg" alt></p><p>可以愉快地检索分析和做图了<br><img src="https://xqimg.imedao.com/16ada7ff90ee5e93fe842855.jpg" alt></p><p>待测试能不能不删除index 直接更新</p>]]></content>
    
    <summary type="html">
    
      使用apm时，处理数据时发现 有的字段没法检索，发现是没有建立mapping的原因，可以看到有个小三角，
一般这种情况下， 在 management &gt; index Patterns 中点击刷新就行



但是跟预期不同的是，fields数量并没有增加，还是建立不了mapping.



尝试修改mapping文件 发现只有通过api的方式修改，只能全量更新，而且点击refresh field l
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Elasticsearch APM Server 配置</title>
    <link href="https://blog.dappwind.com/2019/04/03/"/>
    <id>https://blog.dappwind.com/2019/04/03/</id>
    <published>2019-04-03T10:32:29.000Z</published>
    <updated>2020-04-06T12:00:08.744Z</updated>
    
    <content type="html"><![CDATA[<h2 id="apm-server-yml-配置文件"><a href="#apm-server-yml-配置文件" class="headerlink" title="apm-server.yml 配置文件"></a>apm-server.yml 配置文件</h2><figure class="highlight plain"><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">output.elasticsearch.hosts: [ip1,ip2,ip3,ip4]</span><br><span class="line">queue.mem.events: 51200</span><br><span class="line">output.elasticsearch.workers: 4</span><br><span class="line">output.elasticsearch.bulk_max_size: 5120</span><br><span class="line"></span><br><span class="line"># 开启RUM</span><br><span class="line">apm-server.rum.enabled: true</span><br><span class="line">apm-server.rum.allow_origins: [&apos;*&apos;]</span><br><span class="line">apm-server.rum.library_pattern: &quot;node_modules|bower_components|~&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#配置开启监控api</span><br><span class="line">http.enabled: true</span><br><span class="line">http.port: 5066</span><br><span class="line">http.host: &quot;xx.xx.xx.xx&quot;</span><br><span class="line">#随后访问 xx.xx.xx.xx:5066/stats 即可</span><br></pre></td></tr></table></figure><p>查看配置</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apm-server export config</span><br></pre></td></tr></table></figure><p>修改配置<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd /etc/apm-server</span><br></pre></td></tr></table></figure></p><p>查看日志<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd /var/log/apm-server</span><br></pre></td></tr></table></figure></p><p>验证config<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apm-server test config</span><br></pre></td></tr></table></figure></p><p>验证output<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apm-server test output</span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      apm-server.yml 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


output.elasticsearch.hosts: [ip1,ip2,ip3,ip4]
queue.mem.events: 51200
output.elasticsearch.workers: 4
output.elasticsearch.bulk_max_size: 5
    
    </summary>
    
    
      <category term="Elasticsearch APM" scheme="https://blog.dappwind.com/tags/Elasticsearch-APM/"/>
    
  </entry>
  
  <entry>
    <title>常用个人网页部署方式</title>
    <link href="https://blog.dappwind.com/2019/02/20/"/>
    <id>https://blog.dappwind.com/2019/02/20/</id>
    <published>2019-02-20T08:42:36.000Z</published>
    <updated>2020-04-06T12:00:08.744Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="1-静态内容"><a href="#1-静态内容" class="headerlink" title="1.静态内容"></a>1.静态内容</h1><hr><h2 id="github-pages"><a href="#github-pages" class="headerlink" title="github pages"></a>github pages</h2><p>优点</p><ul><li>方便 用的人多</li></ul><p>缺点</p><ul><li>访问慢 不稳定</li><li>无CI,不过外部工具也很多</li></ul><h2 id="gitlab-pages"><a href="#gitlab-pages" class="headerlink" title="gitlab pages"></a>gitlab pages</h2><p>优点</p><ul><li>有CI 可以持续集成</li></ul><p>缺点</p><ul><li>CI编译慢，不稳定</li></ul><h2 id="coding"><a href="#coding" class="headerlink" title="coding"></a>coding</h2><p>优点</p><ul><li>国内访问快</li></ul><p>缺点</p><ul><li><p>需要在页面上给coding打广告</p></li><li><p>第一次打开会出现广告</p></li></ul><h2 id="虚拟主机"><a href="#虚拟主机" class="headerlink" title="虚拟主机"></a>虚拟主机</h2><p>优点 </p><ul><li>稳定 快</li></ul><p>缺点</p><ul><li>贵，需要备案</li></ul><h2 id="阿里云和腾讯云的-对象存储"><a href="#阿里云和腾讯云的-对象存储" class="headerlink" title="阿里云和腾讯云的 对象存储"></a>阿里云和腾讯云的 对象存储</h2><p>优点 </p><ul><li>快，基本不要钱，稳定，支持https</li></ul><p>缺点</p><ul><li><p>需要一个备案过的域名</p></li><li><p>无CI，但是有api</p></li></ul><hr><h1 id="2-动态内容"><a href="#2-动态内容" class="headerlink" title="2.动态内容"></a>2.动态内容</h1><hr><h2 id="heroku"><a href="#heroku" class="headerlink" title="heroku"></a>heroku</h2><blockquote><p><a href="https://devcenter.heroku.com/" target="_blank" rel="noopener">https://devcenter.heroku.com/</a></p></blockquote><h2 id="ibm-bluemix"><a href="#ibm-bluemix" class="headerlink" title="ibm bluemix"></a>ibm bluemix</h2><blockquote><p><a href="https://www.ibm.com/cloud-computing/bluemix/node/4471" target="_blank" rel="noopener">https://www.ibm.com/cloud-computing/bluemix/node/4471</a></p></blockquote><h2 id="google-app-engine"><a href="#google-app-engine" class="headerlink" title="google app engine"></a>google app engine</h2><blockquote><p><a href="https://console.cloud.google.com/" target="_blank" rel="noopener">https://console.cloud.google.com/</a></p></blockquote><h2 id="阿里腾讯学生机"><a href="#阿里腾讯学生机" class="headerlink" title="阿里腾讯学生机"></a>阿里腾讯学生机</h2><blockquote><p><a href="https://cloud.tencent.com/act/campus" target="_blank" rel="noopener">https://cloud.tencent.com/act/campus</a></p></blockquote><hr><h1 id="3-CI"><a href="#3-CI" class="headerlink" title="3.CI"></a>3.CI</h1><hr><h2 id="codeship-CI介绍"><a href="#codeship-CI介绍" class="headerlink" title="codeship CI介绍"></a>codeship CI介绍</h2><blockquote><p><a href="https://app.codeship.com" target="_blank" rel="noopener">https://app.codeship.com</a></p></blockquote><h2 id="travis-ci"><a href="#travis-ci" class="headerlink" title="travis-ci"></a>travis-ci</h2><blockquote><p><a href="https://docs.travis-ci.com/user/deployment/pages/" target="_blank" rel="noopener">https://docs.travis-ci.com/user/deployment/pages/</a></p></blockquote><h2 id="daocloud-docker-CI"><a href="#daocloud-docker-CI" class="headerlink" title="daocloud docker CI"></a>daocloud docker CI</h2><blockquote><p><a href="https://daocloud.io" target="_blank" rel="noopener">https://daocloud.io</a></p></blockquote><h2 id="docker-hub-CI-介绍"><a href="#docker-hub-CI-介绍" class="headerlink" title="docker hub CI 介绍"></a>docker hub CI 介绍</h2><blockquote><p><a href="https://hub.docker.com" target="_blank" rel="noopener">https://hub.docker.com</a></p></blockquote><h1 id="4-私有js-cdn方案"><a href="#4-私有js-cdn方案" class="headerlink" title="4.私有js cdn方案"></a>4.私有js cdn方案</h1><blockquote><p><a href="https://www.jsdelivr.com/" target="_blank" rel="noopener">https://www.jsdelivr.com/</a></p></blockquote><hr><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><hr><blockquote><p>所以比较好的方案是 github私有仓库，codeship做CI，js用jsdelivr，阿里云和腾讯云的对象存储来存放网页</p></blockquote><h1 id="实践例子"><a href="#实践例子" class="headerlink" title="实践例子"></a>实践例子</h1><p>hexo 搭建博客<br>theme模板 链接都添加 index.html</p><p>使用 ali-oss 上传到 阿里OSS</p><p>在codeship上配置CI</p><p>以后直接新增 .md 文件即可实现发布</p><p>上传脚本</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>)</span><br><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>)</span><br><span class="line"><span class="keyword">const</span> OSS = <span class="built_in">require</span>(<span class="string">'ali-oss'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> promisifyReaddir = util.promisify(fs.readdir)</span><br><span class="line"><span class="keyword">const</span> promisifyStat = util.promisify(fs.stat)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 阿里 OSS access key 拥有对 OSS 的全部权限</span></span><br><span class="line"><span class="keyword">const</span> ALIOSSKEY = &#123;</span><br><span class="line">  key: <span class="string">'......'</span>,</span><br><span class="line">  secret: <span class="string">'.......'</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> client = <span class="keyword">new</span> OSS(&#123;</span><br><span class="line">  <span class="comment">// 请填写你的 Bucket 对应的 region</span></span><br><span class="line">  region: <span class="string">'oss-cn-beijing'</span>,</span><br><span class="line">  accessKeyId: ALIOSSKEY.key,</span><br><span class="line">  accessKeySecret: ALIOSSKEY.secret,</span><br><span class="line">  <span class="comment">// 请填写对应的 Bucket 名字</span></span><br><span class="line">  bucket: <span class="string">'...'</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> publicPath = path.resolve(__dirname, <span class="string">'./blog'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">run</span>(<span class="params">proPath = <span class="string">''</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> dir = <span class="keyword">await</span> promisifyReaddir(<span class="string">`<span class="subst">$&#123;publicPath&#125;</span><span class="subst">$&#123;proPath&#125;</span>`</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; dir.length; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> stat = <span class="keyword">await</span> promisifyStat(path.resolve(<span class="string">`<span class="subst">$&#123;publicPath&#125;</span><span class="subst">$&#123;proPath&#125;</span>`</span>, dir[i]))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (stat.isFile()) &#123;</span><br><span class="line">      <span class="keyword">const</span> fileStream = fs.createReadStream(path.resolve(<span class="string">`<span class="subst">$&#123;publicPath&#125;</span><span class="subst">$&#123;proPath&#125;</span>`</span>, dir[i]))</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`上传文件: <span class="subst">$&#123;proPath&#125;</span>/<span class="subst">$&#123;dir[i]&#125;</span>`</span>)</span><br><span class="line">      <span class="keyword">const</span> result = <span class="keyword">await</span> client.putStream(<span class="string">`<span class="subst">$&#123;proPath&#125;</span>/<span class="subst">$&#123;dir[i]&#125;</span>`</span>, fileStream)</span><br><span class="line">      <span class="comment">// console.log(result)</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (stat.isDirectory()) &#123;</span><br><span class="line">      <span class="keyword">await</span> run(<span class="string">`<span class="subst">$&#123;proPath&#125;</span>/<span class="subst">$&#123;dir[i]&#125;</span>`</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">run()</span><br></pre></td></tr></table></figure><h2 id="codeship-配置命令"><a href="#codeship-配置命令" class="headerlink" title="codeship 配置命令"></a>codeship 配置命令</h2><figure class="highlight bash"><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">npm install</span><br><span class="line">npm run build</span><br><span class="line">npm run upload</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      1.静态内容



github pages
优点

 * 方便 用的人多

缺点

 * 访问慢 不稳定
 * 无CI,不过外部工具也很多

gitlab pages
优点

 * 有CI 可以持续集成

缺点

 * CI编译慢，不稳定

coding
优点

 * 国内访问快

缺点

 * 需要在页面上给coding打广告
   
   
 * 第一次打开会出现广告
   
   

虚拟
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>可视化活动页配置平台</title>
    <link href="https://blog.dappwind.com/2018/12/28/"/>
    <id>https://blog.dappwind.com/2018/12/28/</id>
    <published>2018-12-28T06:29:51.000Z</published>
    <updated>2020-04-06T12:00:08.743Z</updated>
    
    <content type="html"><![CDATA[<h1 id="可视化活动页配置平台"><a href="#可视化活动页配置平台" class="headerlink" title="可视化活动页配置平台"></a>可视化活动页配置平台</h1><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>因为活动页的特点，就是要求快速上线，经常修改。我们回想下发布活动页时的情况：</p><p>运营会想:</p><ul><li>我要发布个运营页面，为什么还要看前端的排期？为啥不能自己配置，我要自己改代码。</li><li>我要改个文案，前端怎么两个多小时了还没改好，是不是能力有问题？而且改多了还发急，态度不好。</li></ul><p>前端也会吐槽：</p><ul><li>一个活动页，来回改，改了一遍又一遍，提了十几个PR全是文案修改，项目都部署了好几次。</li></ul><p>造成这一现象的原因就是，现有活动页，页面信息都是前端直接写在项目里，靠发版来修改。或者后端调用数据库，页面变量定义不灵活。而运营活动页会经常修改，有很多重复劳动。</p><p>所以就有当前活动页发布时的痛点：</p><h4 id="1-修改需要改代码"><a href="#1-修改需要改代码" class="headerlink" title="1.修改需要改代码"></a>1.修改需要改代码</h4><p>改动由前端编写代码完成，运营想改不方便。比如嘉年华的宣传页上的嘉宾及演讲内容，每次都要改很多次，浪费很多时间。</p><h4 id="2-结构类似，重复劳动"><a href="#2-结构类似，重复劳动" class="headerlink" title="2.结构类似，重复劳动"></a>2.结构类似，重复劳动</h4><p>专题页，页面相似，前端往往机械劳动。比如之前的保险专题页，页面结构相似，如果每次都重新开发，没有技术含量。</p><h4 id="3-不能实时预览"><a href="#3-不能实时预览" class="headerlink" title="3.不能实时预览"></a>3.不能实时预览</h4><p>客户想预览 必须发版，周期很长。<br>比如有的活动页需要客户审核，就要每次都发线上，客户让修改后要再发版，时效性不好。（比如给丹书铁券大V定制的铁券一号基金宣传页，基金经理看了后，感觉有个文案写错了，或者头像不好看 要换头像）现在都要靠发一次线上来解决。</p><p>所以准备搭建一个 运营活动页配置后台，前期可以通过该后台修改活动页面文案、图片等。后期可直接通过该平台生成并发布活动页。</p><p>目前已在 2019雪球中概活动页 和 中概投票页中使用，帮助采坑，发现了一些bug，后来都修复了。</p><p>做了两个动图，先展示下能实现的功能</p><ul><li>修改专题页</li></ul><p><img src="https://xqimg.imedao.com/167f41b2b80113fd7b7aaca3.gif" alt></p><ul><li>修改嘉年华活动页</li></ul><p><img src="https://xqimg.imedao.com/167f4244597133fdec5e4903.gif" alt></p><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><h4 id="1-页面结构类似的情况"><a href="#1-页面结构类似的情况" class="headerlink" title="1.页面结构类似的情况"></a>1.页面结构类似的情况</h4><p>比如 活动页、专题页、基金宣传页<br>可以抽离出通用模板，以后再配置活动页，只需在后台新增json就可以实现发布页面</p><h4 id="2-页面数据比较多的情况"><a href="#2-页面数据比较多的情况" class="headerlink" title="2.页面数据比较多的情况"></a>2.页面数据比较多的情况</h4><p>比如 嘉年华宣传页 嘉宾列表、会议时间表等 都很多，可以把数据由前端写死改为json实时数据库，json灵活定义。</p><blockquote><p>使用介绍请直接跳至后面的 <code>编辑后台</code> </p></blockquote><h1 id="框架"><a href="#框架" class="headerlink" title="框架"></a>框架</h1><p><img src="https://xqimg.imedao.com/16a637ba1713a99a3fe1881b.jpg" alt><br>包括3个部分，有一个编辑的后台, 数据存储的数据库和页面渲染的前端项目</p><p>图中间的箭头 传递的是活动页面信息，采用的是json格式的数据。一条json数据就包含了 模板中的全部变量，通过这个数据就可以渲染出一个活动页。</p><p>而要实现这个，就要提取页面模板。所以先说下框架图中的<code>抽取模板</code>部分</p><h2 id="抽取模板"><a href="#抽取模板" class="headerlink" title="抽取模板"></a>抽取模板</h2><p><img src="https://xqimg.imedao.com/16a637c3ad13a5ba3fc46104.jpg" alt></p><p>图上是之前设计师设计的雪球保险活动页，这是4个活动页拼到一起了，每一竖排是一个活动页。因为当时就跟设计师约定好了准备采取了模板的方式，所以活动页结构类似，新配置一个活动页就很方便，一条json数据可以生成一个页面。这是如何实现的呢？</p><h4 id="抽取骨架"><a href="#抽取骨架" class="headerlink" title="抽取骨架"></a>抽取骨架</h4><p><img src="https://xqimg.imedao.com/16a637cf5a53a4033fe5ee65.jpg" alt></p><p>我们可以看到，这4个页面结构类似，我们抽取4个页面相同的骨架图，这个骨架图就是模板，包含了所有不变的内容，而那些会变得内容，我们存在json里。右边这个代码就是json, json是采取  名称：内容 的键值对的数据格式，内容和名称一一对应，每一行都是一个对应关系。</p><h4 id="骨架分配变量名"><a href="#骨架分配变量名" class="headerlink" title="骨架分配变量名"></a>骨架分配变量名</h4><p><img src="https://xqimg.imedao.com/16a637de4e33a5df3fb9ecf0.jpg" alt></p><p>随后，我们要给骨架图中对应的变量分配变量名，就是找到和json数据中的对应关系。</p><p>上面是个头图我们取名为 banner_img 随后是几组保险列表，每一组叫一个section<br>最后是一个底部图 footer_img</p><p>我们能看到在最右侧的json中 能看到这3部分，banner_img 和 footer_img后面的值都是一个网址 以png结尾， 这是个cdn图片资源地址，至于如何生成这个url, 开发了专门的小工具，后面会讲到。</p><p>sections 部分 是个数组，里面有3个，就相当于个子模块</p><h4 id="子模块"><a href="#子模块" class="headerlink" title="子模块"></a>子模块</h4><p><img src="https://xqimg.imedao.com/16a637e396b3a4183fd22eb5.jpg" alt></p><p>右边这个图是抽出其中一个来讲</p><p>这个部分包括一个 小标题 和3个保险卡片</p><p>标题取名为title 对应的是个url资源地址</p><p>卡片部分叫 items</p><p>每个卡片 包括一个图片 标题 价格 按钮等，对应的值在右侧json 绿框中可以看到。</p><h4 id="页面样式-amp-信息"><a href="#页面样式-amp-信息" class="headerlink" title="页面样式&amp;信息"></a>页面样式&amp;信息</h4><p><img src="https://xqimg.imedao.com/16a637eb9143a9d83fe42c44.jpg" alt></p><p>其他的还有些页面样式和信息<br>比如页面背景颜色，卡片背景颜色，文字颜色，微信分享信息，微信分享图等。</p><p>至此一个活动页的json 就定义完了，遇到相似的活动页，我们只要修改下json里面的数据就可以了。</p><h4 id="峰会活动页"><a href="#峰会活动页" class="headerlink" title="峰会活动页"></a>峰会活动页</h4><p><img src="https://xqimg.imedao.com/16a637f232d3a9e33fea3390.jpg" alt></p><p>下面 是另一个应用场景，线下活动页，比如每年的嘉年华，和各种峰会。这些活动页页面信息特别多，而且变动频繁，经常一个活动下来 要改动十几版。使用配置工具，应该会方便很多。</p><p>图中两个活动页 分别是去年的嘉年华和即将举办的中概峰会页面，页面结构大概相似，也可以分为几大块</p><p>然后页面信息有修改的话，只需修改这个json即可。应该是比之前方便了些，因为之前上线一个活动能收到好多修改文案的代码合并请求，现在很少看到了。</p><blockquote><p>总结来说，这个json的提取，即模板的提取，需要我们的设计师们的支持，在设计活动页时，可以按照某个约定好的模板出设计资源，运营同事拿到页面资源后就可以自行配置活动页了。</p></blockquote><h2 id="模板渲染"><a href="#模板渲染" class="headerlink" title="模板渲染"></a>模板渲染</h2><p><img src="https://xqimg.imedao.com/16a637f806039ef13fec34de.jpg" alt><br>接下来简单说下<br>是怎么从json数据生成出一个活动页的<br>采用nodejs 和 nuxt.js<br>nuxt.js 采用vue语法，实现服务端渲染，node服务器直接输出渲染好的活动页，有对搜索引擎友好，加载速度快等优点，然后vue这个框架用起来比较简单，适合做简单交互的活动页，很省事。</p><p><img src="https://xqimg.imedao.com/16a637feeb53a9ed3fd3fa7f.jpg" alt></p><p>左图展示了<br>页面服务端渲染流程，提供了asyncData() 这个函数，用于服务端获取数据，所以，如右侧所示，在这个函数里获取页面数据接口，这个接口也是node写的，通过读取数据库把相应的json数据传过来，有30s的缓存。</p><p>下方是模板渲染代码，可以看到 里面有我们之前定义的变量，页面渲染时，会把json变量里的内容放在页面的相应位置上。实现通过json 渲染一个页面</p><h2 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h2><p><img src="https://xqimg.imedao.com/16a63804d563a4403feee10e.jpg" alt></p><p>页面文案和配置信息采用json格式，前端开发人员可灵活定义。<code>文案、css样式、图片链接</code>都可以自由定义。数据存储采用 json格式转string格式存数据库，有两份。 一份线上 采用统用数据库，一份配置后台预览用，采用firebase实时数据库。</p><ul><li><p>线上数据</p><p>key 由链接拼接 比如 baoxian/child  =&gt; baoxian-child<br>value 为json 转string存储<br>提供接口可查询 由key查value</p></li><li><p>预览数据</p><blockquote><p>firebase 一款方便的实时数据库，已被谷歌收购 <a href="https://firebase.google.com/" target="_blank" rel="noopener">https://firebase.google.com/</a></p></blockquote><blockquote><p>wilddog firebase国内模仿版 <a href="https://www.wilddog.com/" target="_blank" rel="noopener">https://www.wilddog.com/</a></p></blockquote></li></ul><p>线上服务的采用mysql ，因为我们的页面结构用的是json 所以转成string存到数据库里，约定页面链接为两级，key由页面链接拼接而成。由node提供接口，能简单通过key 查 value</p><p>另一部分是实时数据库部分</p><p>因为想实现实时的页面预览，所以采用google的firebase实时数据库，能实现实时存储和同步实时json数据。能够实现 我们在后台编辑，页面能实时看到变动的效果。</p><p>我们约定，进入实时调试模式，只需在链接后添加 ?snowfire=true<br><img src="https://xqimg.imedao.com/16a6380b7f63aa0f3fef4aca.jpg" alt><br>这也是通过代码来实现的，<br>mounted 函数是在客户端渲染时触发，如果发现url中有snowfire参数为true,<br>就读取firebase数据，覆盖之前的数据，并开启监听，json数据一旦有变化，就能实现页面无刷新更新。无刷新更新这个是由vue引擎的双向绑定实现的。</p><h2 id="编辑后台及使用说明"><a href="#编辑后台及使用说明" class="headerlink" title="编辑后台及使用说明"></a>编辑后台及使用说明</h2><p><img src="https://xqimg.imedao.com/16a63812d803aa343f99d3d6.jpg" alt><br>后台是基于我们的雪球CRM通用框架开发的，目前已在多个项目中使用，蛋卷新的CRM，mpaas,和大数据的配置后台，以及正在开发的行情CRM。ui 和组件库 是阿里的antd，开发起来比较方便。</p><p><img src="https://xqimg.imedao.com/16a6381abaa3a48a3fe30626.jpg" alt></p><p>如果要对文案进项修改，先在左上方的输入框中选择要修改的数据，是个二级菜单，选择完后，右边会出现 这个页面的线上页面地址和 预览地址， 区别就是我们约定的参数不同，预览地址后面加了 ？snowfire=true 。 直接点击这两个链接会新窗口打开页面。一般我们点击预览页面的链接来进行修改。</p><p>下方是对应的json 数据 我们可以进行修改，里面的css变量 还可以看到颜色选择器</p><p>点击小三角可以 展开 子模块，绿色的部分是文案 直接点击修改就行。</p><p>右上方有 <code>发布预览</code> 和 <code>发布线上</code> 两个按钮。</p><p><code>发布预览</code> 就是相当于保存，是发布到实时数据库。一般有修改后，直接点击<code>发布预览</code>即可。预览页面就可以看到变动了。</p><p><code>发布线上</code> 是发布到线上数据，当预览页面修改没问题时，就可以发布线上了。</p><p>当修改数据出现问题时，只要不点<code>发布预览</code>, 直接刷新就可以恢复之前的数据。</p><p>如果不慎点了<code>发布预览</code>，想要还原的话，把鼠标移到<code>恢复</code>按钮处，会出现两个选项<br><img src="https://xqimg.imedao.com/169b4c7ece024453fe1aee31.jpg" alt></p><ul><li><p><code>恢复上次预览数据</code> 会将数据恢复到 上一次发布时。</p></li><li><p><code>从线上数据恢复</code> 会从线上获取数据，更新到预览数据中。</p></li></ul><p>如果想要新增 复制 和删除的话， 点击前边的小方块，里面有选项。<br><img src="https://xqimg.imedao.com/16a63824ed139f533feea08a.jpg" alt></p><p>如果遇到想修改排序的话，<br>拖动最前面的 6个小点，可以直接拖动排序，如图所示。<br><img src="https://xqimg.imedao.com/169a4a5ffcd433fe812118d8.gif" alt></p><p>之前提到，json里的图片资源都是链接的形式，如果想换个图片怎么获得新图片的链接呢？<br>之前的方法是在雪球里面发个帖子，然后把帖子里面的图片地址复制出来，很不方便。</p><p>所以开发了个图片上传的小工具，在运营助手下面， 有个图片上传的选项，点击后会新开一个窗口，可以把图片批量拖动到方框里 或者点击方框 选择图片上传。然后下方会出现图片的预览图和对应的cdn地址，我们可以直接复制这个地址，粘贴到编辑器的相应位置。<br><img src="https://xqimg.imedao.com/169a4a6a5884e9843fd4ca08.gif" alt></p><h2 id="新建流程"><a href="#新建流程" class="headerlink" title="新建流程"></a>新建流程</h2><h4 id="1-已有模板时"><a href="#1-已有模板时" class="headerlink" title="1.已有模板时"></a>1.已有模板时</h4><p>新增页面示例<br><img src="https://xqimg.imedao.com/167f42c3e1c153fd65babf24.gif" alt></p><p>比如新建一个中年人保险活动页，前端已经搭建了模板，设计按照约定的页面结构提供素材，开发人员或者运营人员打开后台，点击右上角，切换成开发者模式，在baoxian栏下 复制一个json信息，比如复制child的json</p><p><img src="https://xqimg.imedao.com/16a6382d7c039f5b3fd3c42f.jpg" alt></p><p>将名字改为 adult, 点击预览，随后切换回普通模式，选择新建的数据进行修改。</p><p>这时页面已经建立，可以数据选择框右面看到相应网址。用浏览器打开新建的链接，当然内容还是child的内容。</p><p>这时就可以替换内容了，建议关闭实时预览节省流量。</p><p>替换完，预览没问题就可以发给审核人员或者客户看下，有问题也可以直接后台修改。</p><p>确认没问题后，点击发布，即可更新线上数据库内容。<br>至此一个活动页就搭建完成了。</p><h4 id="2-没有模板时"><a href="#2-没有模板时" class="headerlink" title="2.没有模板时"></a>2.没有模板时</h4><p>大部分情况下，已有模板不能满足设计要求。前端人员需要先开发出页面，抽离出变量，变量的精细程度看具体页面要求。可以全部配置成变量，也可以选择那些容易变动的或者不确定的内容替换。</p><p>开发时可以先不调接口和firebase,直接在data中写JsonData这个变量，内部名字尽量取通俗易懂的。当整体开发完成后，切换为开发者模式，将JsonData复制到后台实时数据库中，切换为 code 模式，将json粘贴上去，格式化。由于插件问题，我们需要再次切换为 tree模式，在tree模式下随便改动一下 才会生效。然后点击预览，数据就同步到实时数据库了。</p><p><img src="https://xqimg.imedao.com/16a6383c23f3a6843fedfdb3.jpg" alt></p><p>这时将页面数据切换为实时数据库数据</p><p>部署前端项目，就可以交由运营配置页面信息了。流程如1。</p>]]></content>
    
    <summary type="html">
    
      可视化活动页配置平台
背景
因为活动页的特点，就是要求快速上线，经常修改。我们回想下发布活动页时的情况：

运营会想:

 * 我要发布个运营页面，为什么还要看前端的排期？为啥不能自己配置，我要自己改代码。
 * 我要改个文案，前端怎么两个多小时了还没改好，是不是能力有问题？而且改多了还发急，态度不好。

前端也会吐槽：

 * 一个活动页，来回改，改了一遍又一遍，提了十几个PR全是文案修改，项目
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Cli</title>
    <link href="https://blog.dappwind.com/2018/11/07/"/>
    <id>https://blog.dappwind.com/2018/11/07/</id>
    <published>2018-11-07T03:13:25.000Z</published>
    <updated>2020-04-06T12:00:08.743Z</updated>
    
    <content type="html"><![CDATA[<h1 id="nodejs-编写简单-cli"><a href="#nodejs-编写简单-cli" class="headerlink" title="nodejs 编写简单 cli"></a>nodejs 编写简单 cli</h1><p><img src="https://ws1.sinaimg.cn/large/6b201a41ly1fwzeg9nwsuj20c307y3zc.jpg" alt></p><p>Command Line Interface，顾名思义是一种通过命令行来交互的工具或者说应用。SPA应用中常用的如vue-cli, angular-cli, node.js开发搭建express-generator，还有我们最常用的webpack，npm等。他们是web开发者的辅助工具，旨在减少低级重复劳动，专注业务提高开发效率，规范develop workflow。</p><p>CLI的根据不同业务场景有不同的功能，但万变不离其宗，本质都是通过命令行交互的方式在本地电脑运行代码，执行一些任务。</p><p>CLI有什么好处？</p><p>我们可以从工作中总结繁杂、有规律可循、或者简单重复劳动的工作用CLI来完成，只需一些命令，快速完成简单基础劳动。以下是我对现有工作中的可以用CLI工具实现的总结举例：</p><ul><li>快速生成应用模板，如vue-cli等根据与开发者的一些交互式问答生成应用框架</li><li>创建module模板文件，如angular-cli，创建component,module；sequelize-cli 创建与mysql表映射的model等</li><li>服务启动，如ng serve</li><li>eslint，代码校验，如vue,angular，基本都具备此功能</li><li>自动化测试 如vue,angular，基本都具备此功能</li><li>编译build，如vue,angular，基本都具备此功能</li><li>*编译分析，利用webpack插件进行分析</li><li>*git操作</li><li>*生成的代码上传CDN</li><li>*还可以是小工具用途的功能，如http请求api、图片压缩、生成雪碧图等等，只要你想做的都能做<br>总体而言就是一些快捷的操作替代人工重复劳动，提升开发效率。</li></ul><p>与npm scripts的对比</p><p>npm scripts也可以实现开发工作流，通过在package.json 中的scripts对象上配置相关npm 命令，执行相关js来达到相同的目的；</p><p>但是cli工具与npm scripts相比有什么优势呢?</p><p>npm scripts是某个具体项目的，只能在该项目内使用，cli可以是全局安装的，多个项目使用；<br>使用npm scripts 在业务工程里面嵌入工作流，耦合太高；使用cli 可以让业务代码工作流相关代码剥离，业务代码专注业务<br>cli工具可以不断迭代开发，演进，沉淀。</p><p>下面就是nodejs 实现一个简单cli</p><p><img src="https://ws1.sinaimg.cn/large/6b201a41ly1fwzchofm4rg20eq05mglq.gif" alt></p><h2 id="hello-world"><a href="#hello-world" class="headerlink" title="hello world"></a>hello world</h2><p>nodejs的cli，本质就是跑node脚本，大家都会：<br><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="comment">// index.js</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'hello xueqiu'</span>)</span><br></pre></td></tr></table></figure></p><p>然后命令行调用</p><blockquote><p>node index.js</p></blockquote><h2 id="输出："><a href="#输出：" class="headerlink" title="输出："></a>输出：</h2><blockquote><p>hello world</p></blockquote><p>可以做得更逼真一点，我们在package.json里面的scripts字段上添加一下脚本名：<br><figure class="highlight json"><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">&#123;</span><br><span class="line">    <span class="attr">"scripts"</span>:&#123;</span><br><span class="line">        <span class="attr">"hello"</span>:<span class="string">"node index.js"</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>然后命令行调用：</p><blockquote><p>npm run hello</p></blockquote><p>接下来就说说，如何给这个node脚本起个名字。</p><h2 id="起名字"><a href="#起名字" class="headerlink" title="起名字"></a>起名字</h2><p>姑且，先把这个cli的名字命名为dw-cli，就是我们能够在命令行里面，输入dw-cli，然后它就打印一句hello xueqiu，没有node也没有npm，就是：</p><p>这里，我们需要做几步操作：</p><p>index.js文件顶部声明执行环境：</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="meta">#!/usr/bin/env node</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'hello xueqiu'</span>)</span><br></pre></td></tr></table></figure><p>添加 <code>#!/usr/bin/env node</code> 或者 <code>#!/usr/bin/node</code> ，这是告诉系统，下面这个脚本，使用nodejs来执行。当然，这个系统不包括windows，因为windows下有个JScript的历史遗留物在，会让你的脚本跑不起来。</p><blockquote><p>#!/usr/bin/env node的意思是让系统自己去找node的执行程序。</p></blockquote><blockquote><p>#!/usr/bin/node的意思是，明确告诉系统，node的执行程序在路径为/usr/bin/node。</p></blockquote><p>添加package.json的bin字段。</p><p>可以在index.js当前的目录下执行npm init创建一个package.json，然后在package.json里面，添加一个bin字段：</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">     <span class="attr">"name"</span>: <span class="string">"cli-test"</span>,</span><br><span class="line">     <span class="attr">"version"</span>: <span class="string">"1.0.0"</span>,</span><br><span class="line">     <span class="attr">"bin"</span>:&#123;</span><br><span class="line">         <span class="attr">"dw-cli"</span>:<span class="string">"index.js"</span>  </span><br><span class="line">     &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>bin字段里面写上这个命令行的名字，也就是dw-cli，它告诉npm，里面的js脚本可以通过命令行的方式执行，以dw-cli的命令调用。当然命令行的名字想写什么都可以：</p><p>在当前package.json目录下，打开命令行工具，执行npm link，将当前的代码在npm全局目录下留个快捷方式。<br>npm检测到package.json里面存在一个bin字段，它就同时在全局npm包目录下生成了一个可执行文件：</p><blockquote><p>npm root -g 这个命令可以看到npm的全局位置</p></blockquote><p><img src="https://ws1.sinaimg.cn/large/6b201a41ly1fwzcpxojqyj20cb07qmxv.jpg" alt></p><p>当我们在系统命令行直接执行dw-cli的时候，实际上就是执行这里的脚本。</p><p>因为安装node的时候，npm将这个目录配置为系统变量环境了，当你执行命令的时候，系统会先找系统命令和系统变量，然后到变量环境里面去查找这个命令名，然后找到这个目录后，发现匹配上了该命令名的可执行文件，接着就直接执行它。vue-cli也好，webpack-cli也好，都是这样执行的。</p><p>这样，你的第一个cli脚本就成功安装了，可以在命令行里面，直接敲你的cli名字，看看结果输出吧。</p><p><img src="https://ws1.sinaimg.cn/large/6b201a41ly1fwzdmz6ugzg20eq05mdfp.gif" alt></p><p>另外，如果你仅希望你的cli脚本仅在项目里执行，则需要在你项目里面新建一个目录，重复上述的操作，只是在第三步的时候，不要llink到全局里面去，而是使用npm i -D file:&lt;你的脚本cli目录路径&gt;，把它当成项目的依赖安装到node_modules里面去，如果安装成功，那么在项目的package.json你会看到多了一条依赖，这条依赖的值不是版本号，而是你脚本的路径。然后在node_modules里面会有一个.bin目录，里面就存放着你的可执行文件。</p><h2 id="参数读取-process-argv"><a href="#参数读取-process-argv" class="headerlink" title="参数读取:process.argv"></a>参数读取:process.argv</h2><p>名字有了，输出也有了，看看我们跟那些大名鼎鼎的cli工具，在形式上还差点啥？对了，人家可以支持不同参数选项的，还可以根据输入的不同，产生不同的结果。<br>这样吧，我们给这个cli加一个功能，既然叫dw-cli，那不能只会hello world吧，必须要见谁就说hello才行：</p><blockquote><p>dw-cli older</p></blockquote><h2 id="输出"><a href="#输出" class="headerlink" title="输出"></a>输出</h2><blockquote><p>hello older</p></blockquote><p>虽然这个功能很简单，但是至少也是实现了“根据输入的不同，产生不同结果”的效果。</p><p>命令行上的参数，可以通过process这个变量获取，process是一个全局对象而不是一个包，不需要通过require引入。通过process这个对象我们可以拿到当前脚本执行环境等一系列信息，其中就包括命令行的输入情况，这个信息，保存在process.argv这个属性里。我们可以打印一下：</p><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(process.argv);</span><br></pre></td></tr></table></figure><p><img src="https://ws1.sinaimg.cn/large/6b201a41ly1fwzdogmycmg20eq05mwef.gif" alt></p><p>打印结果：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hello  [ &apos;/usr/local/bin/node&apos;, &apos;/usr/local/bin/dw-cli&apos;, &apos;xq&apos;]</span><br></pre></td></tr></table></figure></p><p>可以看出，argv是个数组，前两位是固定的，分别是node程序的路径和脚本存放的位置，从第三位开始才是额外输入的内容。那么实现上面的功能就很简单了，只要读取argv数组的第三位，然后输出出来就可以了。</p><figure class="highlight plain"><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">//index.js</span><br><span class="line">console.log(`hello $&#123;process.argv[2]||&apos;world&apos;&#125;`)</span><br></pre></td></tr></table></figure><p>npm社区中也有一些优秀的命令行参数解析包，比如commander.js等等</p><blockquote><p><a href="https://www.npmjs.com/package/commander" target="_blank" rel="noopener">https://www.npmjs.com/package/commander</a></p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> program = <span class="built_in">require</span>(<span class="string">'commander'</span>);</span><br><span class="line"></span><br><span class="line">program</span><br><span class="line">  .command(<span class="string">'create &lt;type&gt; [name] [otherParams...]'</span>)</span><br><span class="line">  .alias(<span class="string">'c'</span>)</span><br><span class="line">  .description(<span class="string">'Generates new code'</span>)</span><br><span class="line">  .action(<span class="function"><span class="keyword">function</span> (<span class="params">type, name, otherParams</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'type'</span>, type);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'name'</span>, name);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'other'</span>, otherParams);</span><br><span class="line">    <span class="comment">// 在这里执行具体的操作</span></span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">program.parse(process.argv);</span><br></pre></td></tr></table></figure><p>如果你想使用比较复杂的参数或者命令，建议还是用第三方包比较好，手写解析太耗精力了。</p><h2 id="子进程"><a href="#子进程" class="headerlink" title="子进程"></a>子进程</h2><p>现在，你可以自由自在的写你自己的cli脚本了。<br>如果想使用phantom你需要通过node的child_process模块开启子进程，在子进程内调用命令：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; exec &#125; = <span class="built_in">require</span>(<span class="string">'child_process'</span>)</span><br><span class="line">exec(command, (error, stdout, stderr) =&gt; &#123;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>包括系统命令、其他cli命令都可以在这里执行。特别是系统命令。社区上也有一些不错的包，比如shelljs</p><h2 id="美化输出"><a href="#美化输出" class="headerlink" title="美化输出"></a>美化输出</h2><p>希望更人性化一点，比如提供一些友好的输入、提示啊，给你的输出加点颜色区分重点啊，写个简单的进度条啊等等，那么就需要美化一下你的输出了。</p><p>除了颜色这部分，不使用第三方包实现起来非常繁琐复杂，其他的功能，都可以试试自己写。<br>颜色部分使用第三方包colors。</p><p>其他都是由nodejs自带的readline模块实现的。<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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env node</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'hello xq'</span>)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'hello '</span>, process.argv)</span><br><span class="line"><span class="keyword">const</span> readline = <span class="built_in">require</span>(<span class="string">'readline'</span>)</span><br><span class="line"><span class="keyword">const</span> unloadChar = <span class="string">'-'</span></span><br><span class="line"><span class="keyword">const</span> loadedChar = <span class="string">'='</span></span><br><span class="line"><span class="keyword">const</span> rl = readline.createInterface(&#123;</span><br><span class="line">  input: process.stdin,</span><br><span class="line">  output: process.stdout</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">rl.question(<span class="string">'什么命令？ '</span>, answer =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">let</span> time = setInterval(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (i &gt; <span class="number">10</span>) &#123;</span><br><span class="line">      clearInterval(time)</span><br><span class="line">      readline.cursorTo(process.stdout, <span class="number">0</span>, <span class="number">2</span>)</span><br><span class="line">      <span class="comment">// readline.clearScreenDown(process.stdout)</span></span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`hello <span class="subst">$&#123;answer&#125;</span>`</span>)</span><br><span class="line">      process.exit(<span class="number">0</span>)</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    readline.cursorTo(process.stdout, <span class="number">0</span>, <span class="number">1</span>)</span><br><span class="line">    readline.clearScreenDown(process.stdout)</span><br><span class="line">    renderProgress(<span class="string">'saying hello'</span>, i)</span><br><span class="line">    i++</span><br><span class="line">  &#125;, <span class="number">200</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">renderProgress</span>(<span class="params">text, step</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> PERCENT = <span class="built_in">Math</span>.round(step * <span class="number">10</span>)</span><br><span class="line">  <span class="keyword">const</span> COUNT = <span class="number">2</span></span><br><span class="line">  <span class="keyword">const</span> unloadStr = <span class="keyword">new</span> <span class="built_in">Array</span>(COUNT * (<span class="number">10</span> - step)).fill(unloadChar).join(<span class="string">''</span>)</span><br><span class="line">  <span class="keyword">const</span> loadedStr = <span class="keyword">new</span> <span class="built_in">Array</span>(COUNT * step).fill(loadedChar).join(<span class="string">''</span>)</span><br><span class="line">  process.stdout.write(<span class="string">`<span class="subst">$&#123;text&#125;</span>:【<span class="subst">$&#123;loadedStr&#125;</span><span class="subst">$&#123;unloadStr&#125;</span>|<span class="subst">$&#123;PERCENT&#125;</span>%】`</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p><img src="https://ws1.sinaimg.cn/large/6b201a41ly1fwzchofm4rg20eq05mglq.gif" alt></p><p>首先，通过readline.createInterface方法创建一个interface类，这个类下面有一个方法.question，用这个方法在命令行上抛出一个问题，在第二个参数传入一个函数进行监听。一旦用户输入完毕敲下回车，就会触发回调函数。<br>然后我们在回调函数里面写了个计时器，假装我们在处理某些事务。<br>使用readline.cursorTo这个方法，可以改变命令行上的光标的位置。</p><blockquote><p>readline.cursorTo(process.stdout, 0, 0);是移动到第1列第1行上</p></blockquote><blockquote><p>readline.cursorTo(process.stdout, 0, 1);是移动到第1列第2行上</p></blockquote><p>使用readline.clearScreenDown这个方法，是让命令行从当前行开始，到最后一行结束，将这两行之间所有内容清除。<br>renderProgress是自己封装的一个方法，通过process.stdout.write方法输出一行看起来像是进度条的字符串到命令行上。<br>所以在计时器里面，当计数小于10的时候，我们让光标移到第一行上，然后清除所有输出，输出进度条字符串；当计数大于10的时候，我们关掉计时器，清除输出，打印结果。<br>最后不要忘记关掉进程，可以使用interface这个类的.close方法关掉readline进程，也可以直接调用process.exit退出。</p><p>绘制的思路跟canvas绘制动画一样，只不过canvas是清除画布，而命令行这里是通过readline.clearScreenDown清除输出。</p><h2 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h2><p>实现功能为：</p><ul><li>输入 new 命令从github下载一个脚手架模版，然后创建对应的app。</li><li>输入 create 命令可以快速的创建一些样板文件。</li></ul><blockquote><p><a href="https://juejin.im/post/5a90dd62f265da4e9a4973aa" target="_blank" rel="noopener">https://juejin.im/post/5a90dd62f265da4e9a4973aa</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      nodejs 编写简单 cli


Command Line Interface，顾名思义是一种通过命令行来交互的工具或者说应用。SPA应用中常用的如vue-cli, angular-cli, node.js开发搭建express-generator，还有我们最常用的webpack，npm等。他们是web开发者的辅助工具，旨在减少低级重复劳动，专注业务提高开发效率，规范develop workfl
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Vue简易实现</title>
    <link href="https://blog.dappwind.com/2018/09/12/"/>
    <id>https://blog.dappwind.com/2018/09/12/</id>
    <published>2018-09-12T04:55:14.000Z</published>
    <updated>2020-04-06T12:00:08.743Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简易vue框架实现"><a href="#简易vue框架实现" class="headerlink" title="简易vue框架实现"></a>简易vue框架实现</h1><p>找到了个简单代码实现了vue的少许基本功能的例子，有助于了解vue源码，加深对框架的理解。遇到问题也可以从原理方面分析。</p><h2 id="实现目标"><a href="#实现目标" class="headerlink" title="实现目标"></a>实现目标</h2><figure class="highlight html"><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"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;&#123;title&#125;&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">v-model</span>=<span class="string">"name"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#123;name&#125;&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">v-on:click</span>=<span class="string">"clickMe"</span>&gt;</span>click me!<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./observer.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./compile.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./mvvm.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./watcher.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">    <span class="keyword">new</span> Mvvm(&#123;</span></span><br><span class="line"><span class="javascript">      el: <span class="string">'#app'</span>,</span></span><br><span class="line"><span class="undefined">      data: &#123;</span></span><br><span class="line"><span class="javascript">        title: <span class="string">'mvvm title'</span>,</span></span><br><span class="line"><span class="javascript">        name: <span class="string">'mvvm name'</span></span></span><br><span class="line"><span class="undefined">      &#125;,</span></span><br><span class="line"><span class="undefined">      methods: &#123;</span></span><br><span class="line"><span class="javascript">        clickMe: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">          <span class="keyword">this</span>.title = <span class="string">'mvvm code click'</span></span></span><br><span class="line"><span class="undefined">        &#125;,</span></span><br><span class="line"><span class="undefined">      &#125;,</span></span><br><span class="line"><span class="javascript">      mounted: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">        <span class="built_in">window</span>.setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">          <span class="keyword">this</span>.title = <span class="string">'timeout 1000'</span></span></span><br><span class="line"><span class="undefined">        &#125;, 1000)</span></span><br><span class="line"><span class="undefined">      &#125;</span></span><br><span class="line"><span class="undefined">    &#125;)</span></span><br><span class="line"><span class="undefined">  </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="框架图"><a href="#框架图" class="headerlink" title="框架图"></a>框架图</h2><p><img src="https:////xqimg.imedao.com/165cc209b2111e273fd68945.png" alt></p><h2 id="主函数"><a href="#主函数" class="headerlink" title="主函数"></a>主函数</h2><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Mvvm</span> (<span class="params">options</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 数据和方法</span></span><br><span class="line">  <span class="keyword">this</span>.data = options.data</span><br><span class="line">  <span class="keyword">this</span>.methods = options.methods</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 将data 代理到 this</span></span><br><span class="line">  <span class="built_in">Object</span>.keys(<span class="keyword">this</span>.data).forEach(<span class="function"><span class="params">key</span> =&gt;</span></span><br><span class="line">    self.proxyKeys(key)</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 观察&amp;数据劫持</span></span><br><span class="line">  observe(<span class="keyword">this</span>.data)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 模板编译</span></span><br><span class="line">  <span class="keyword">new</span> Compile(options.el, <span class="keyword">this</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 所有事情处理好后执行 mounted 函数</span></span><br><span class="line">  options.mounted.call(<span class="keyword">this</span>) </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Mvvm.prototype = &#123;</span><br><span class="line">  proxyKeys: <span class="function"><span class="keyword">function</span>(<span class="params">key</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line">    <span class="comment">// 这里的 get 和 set 实现了 vm.data.name 和 vm.name 的值同步</span></span><br><span class="line">    <span class="built_in">Object</span>.defineProperty(<span class="keyword">this</span>, key, &#123;</span><br><span class="line">      <span class="keyword">get</span>: function () &#123; </span><br><span class="line">        <span class="keyword">return</span> self.data[key]</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="keyword">set</span>: function (newValue) &#123;</span><br><span class="line">        self.data[key] = newValue</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="编译函数-compile"><a href="#编译函数-compile" class="headerlink" title="编译函数 compile"></a>编译函数 compile</h2><p>将html中的vue指令解析编译 实现绑定</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Compile</span>(<span class="params">el, vm</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.vm = vm</span><br><span class="line">  <span class="keyword">this</span>.el = <span class="built_in">document</span>.querySelector(el)</span><br><span class="line">  <span class="keyword">this</span>.fragment = <span class="literal">null</span></span><br><span class="line">  <span class="keyword">this</span>.init()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Compile.prototype = &#123;</span><br><span class="line">  init: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span>.el) &#123;</span><br><span class="line">      <span class="comment">// 因为遍历解析的过程有多次操作 dom 节点，为提高性能和效率，会先将跟节点 el 转换成文档碎片 fragment 进行解析编译操作，解析完成，再将 fragment 添加回原来的真实 dom 节点中</span></span><br><span class="line">      <span class="keyword">this</span>.fragment = <span class="keyword">this</span>.nodeToFragment(<span class="keyword">this</span>.el)   </span><br><span class="line">      <span class="comment">// 循环处理每个节点</span></span><br><span class="line">      <span class="keyword">this</span>.compileElement(<span class="keyword">this</span>.fragment)</span><br><span class="line">      <span class="comment">// 再加回去</span></span><br><span class="line">      <span class="keyword">this</span>.el.appendChild(<span class="keyword">this</span>.fragment)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'Dom元素不存在'</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  nodeToFragment: <span class="function"><span class="keyword">function</span>(<span class="params">el</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> fragment = <span class="built_in">document</span>.createDocumentFragment()</span><br><span class="line">    <span class="keyword">let</span> child = el.firstChild <span class="comment">// △ 第一个 firstChild 是 text</span></span><br><span class="line">    <span class="keyword">while</span>(child) &#123;</span><br><span class="line">      fragment.appendChild(child) </span><br><span class="line">      <span class="comment">// appendChild 后 原始值为减少</span></span><br><span class="line">      child = el.firstChild</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fragment</span><br><span class="line">  &#125;,</span><br><span class="line">  compileElement: <span class="function"><span class="keyword">function</span>(<span class="params">el</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> childNodes = el.childNodes</span><br><span class="line">    <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line">    <span class="built_in">Array</span>.prototype.forEach.call(childNodes, <span class="function"><span class="keyword">function</span> (<span class="params">node</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">const</span> reg = <span class="regexp">/\&#123;\&#123;(.*)\&#125;\&#125;/</span></span><br><span class="line">      <span class="keyword">const</span> text = node.textContent</span><br><span class="line">      <span class="comment">// 编译解析  v-  和 on：  </span></span><br><span class="line">      <span class="keyword">if</span> (self.isElementNode(node)) &#123;</span><br><span class="line">        self.compile(node)</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (self.isTextNode(node) &amp;&amp; reg.test(text)) &#123;</span><br><span class="line">      <span class="comment">// 编译解析 &#123;&#123;...&#125;&#125;</span></span><br><span class="line">        self.compileText(node, reg.exec(text)[<span class="number">1</span>])</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 子循环</span></span><br><span class="line">      <span class="keyword">if</span> (node.childNodes &amp;&amp; node.childNodes.length) &#123; <span class="comment">// 循环遍历子节点</span></span><br><span class="line">        self.compileElement(node)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  compile: <span class="function"><span class="keyword">function</span> (<span class="params">node</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> nodeAttrs = node.attributes</span><br><span class="line">    <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">Array</span>.prototype.forEach.call(nodeAttrs, <span class="function"><span class="keyword">function</span> (<span class="params">attr</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">const</span> attrName = attr.name</span><br><span class="line">      <span class="keyword">const</span> exp = attr.value</span><br><span class="line">      <span class="keyword">const</span> dir = attrName.substring(<span class="number">2</span>)</span><br><span class="line">      <span class="keyword">if</span> (self.isDirective(attrName)) &#123; <span class="comment">// 如果指令包含 v-</span></span><br><span class="line">        <span class="keyword">if</span> (self.isEventDirective(dir)) &#123; <span class="comment">// 如果是事件指令, 包含 on:</span></span><br><span class="line">          self.compileEvent(node, self.vm, exp, dir)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">// v-model 指令</span></span><br><span class="line">          self.compileModel(node, self.vm, exp)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  compileText: <span class="function"><span class="keyword">function</span> (<span class="params">node, exp</span>) </span>&#123; <span class="comment">// 将 &#123;&#123;abc&#125;&#125; 替换掉</span></span><br><span class="line">    <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line">    <span class="keyword">const</span> initText = <span class="keyword">this</span>.vm[exp]</span><br><span class="line">    <span class="keyword">this</span>.updateText(node, initText) <span class="comment">// 初始化</span></span><br><span class="line">    <span class="keyword">new</span> Watcher(<span class="keyword">this</span>.vm, exp, <span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>&#123; <span class="comment">// 实例化订阅者</span></span><br><span class="line">      self.updateText(node, value)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  compileEvent: <span class="function"><span class="keyword">function</span> (<span class="params">node, vm, exp, dir</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> eventType = dir.split(<span class="string">':'</span>)[<span class="number">1</span>]</span><br><span class="line">    <span class="keyword">const</span> cb = vm.methods &amp;&amp; vm.methods[exp]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (eventType &amp;&amp; cb) &#123;</span><br><span class="line">      node.addEventListener(eventType, cb.bind(vm), <span class="literal">false</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  compileModel: <span class="function"><span class="keyword">function</span> (<span class="params">node, vm, exp</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> val = vm[exp]</span><br><span class="line">    <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line">    <span class="keyword">this</span>.modelUpdater(node, val)</span><br><span class="line">    node.addEventListener(<span class="string">'input'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">const</span> newValue = e.target.value</span><br><span class="line">      self.vm[exp] = newValue <span class="comment">// 实现 view 到 model 的绑定</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  updateText: <span class="function"><span class="keyword">function</span> (<span class="params">node, value</span>) </span>&#123;</span><br><span class="line">    node.textContent = <span class="keyword">typeof</span> value === <span class="string">'undefined'</span> ? <span class="string">''</span> : value</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  modelUpdater: <span class="function"><span class="keyword">function</span>(<span class="params">node, value</span>) </span>&#123;</span><br><span class="line">    node.value = <span class="keyword">typeof</span> value === <span class="string">'undefined'</span> ? <span class="string">''</span> : value</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  isEventDirective: <span class="function"><span class="keyword">function</span>(<span class="params">dir</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> dir.indexOf(<span class="string">'on:'</span>) === <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  isDirective: <span class="function"><span class="keyword">function</span>(<span class="params">attr</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> attr.indexOf(<span class="string">'v-'</span>) === <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  isElementNode: <span class="function"><span class="keyword">function</span>(<span class="params">node</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> node.nodeType === <span class="number">1</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  isTextNode: <span class="function"><span class="keyword">function</span>(<span class="params">node</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> node.nodeType === <span class="number">3</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="数据劫持-observer"><a href="#数据劫持-observer" class="headerlink" title="数据劫持 observer"></a>数据劫持 observer</h2><p>实现数据“劫持”： 数据变动时 进行“劫持” 引发相应操作</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><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 通过 observe 监听数据变化，当数据变化时候，告知 Dep，调用 update 更新数据。</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">Dep</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.subs = []</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Dep.prototype = &#123;</span><br><span class="line">  addSub: <span class="function"><span class="keyword">function</span>(<span class="params">sub</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.subs.push(sub)</span><br><span class="line">  &#125;,</span><br><span class="line">  notify: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.subs.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">sub</span>) </span>&#123;</span><br><span class="line">      <span class="comment">//  对应 watcher 中的 update 方法</span></span><br><span class="line">      sub.update()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</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">observe</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!data || <span class="keyword">typeof</span>(data) !== <span class="string">'object'</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> self = <span class="keyword">this</span></span><br><span class="line">  <span class="built_in">Object</span>.keys(data).forEach(<span class="function"><span class="params">key</span> =&gt;</span></span><br><span class="line">    self.defineReactive(data, key, data[key])</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">defineReactive</span>(<span class="params">data, key, value</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> dep = <span class="keyword">new</span> Dep()</span><br><span class="line">  observe(value) <span class="comment">// 遍历嵌套对象</span></span><br><span class="line">  <span class="built_in">Object</span>.defineProperty(data, key, &#123;</span><br><span class="line">    <span class="keyword">get</span>: function() &#123;</span><br><span class="line">      <span class="comment">// 有target时 才会添加订阅 （对应watcher 中 target 设定）</span></span><br><span class="line">      <span class="keyword">if</span> (Dep.target) &#123; <span class="comment">// 往订阅器添加订阅者</span></span><br><span class="line">        dep.addSub(Dep.target)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> value</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">set</span>: function(newValue) &#123;</span><br><span class="line">      <span class="keyword">if</span> (value !== newValue) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'值发生变化'</span>, <span class="string">'newValue:'</span> + newValue + <span class="string">' '</span> + <span class="string">'oldValue:'</span> + value)</span><br><span class="line">        value = newValue</span><br><span class="line">        dep.notify()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Watcher：-observer-和-编译后html的桥梁"><a href="#Watcher：-observer-和-编译后html的桥梁" class="headerlink" title="Watcher： observer 和 编译后html的桥梁"></a>Watcher： observer 和 编译后html的桥梁</h2><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="comment">// Watcher 订阅者作为 observer 和 compile 之间通信的桥梁，主要做的事情是:</span></span><br><span class="line"><span class="comment">// 1、在自身实例化时往订阅器(dep)里面添加自己</span></span><br><span class="line"><span class="comment">// 2、待 model 变动 dep.notice() 通知时，能调用自身的 update() 方法，并触发 Compile 中绑定的回调</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Watcher</span>(<span class="params">vm, exp, cb</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.cb = cb</span><br><span class="line">  <span class="keyword">this</span>.vm = vm</span><br><span class="line">  <span class="keyword">this</span>.exp = exp</span><br><span class="line">  <span class="keyword">this</span>.value = <span class="keyword">this</span>.get()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Watcher.prototype = &#123;</span><br><span class="line">  update: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.run()</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  run: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> value = <span class="keyword">this</span>.vm.data[<span class="keyword">this</span>.exp]</span><br><span class="line">    <span class="keyword">const</span> oldVal = <span class="keyword">this</span>.value</span><br><span class="line">    <span class="keyword">if</span> (value !== oldVal) &#123;</span><br><span class="line">      <span class="keyword">this</span>.value = value</span><br><span class="line">      <span class="keyword">this</span>.cb.call(<span class="keyword">this</span>.vm, value)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="keyword">get</span>: function() &#123;</span><br><span class="line">    Dep.target = <span class="keyword">this</span> <span class="comment">// 缓存自己</span></span><br><span class="line">    <span class="comment">// 强制执行监听器里的 get 函数 进而添加订阅</span></span><br><span class="line">    <span class="keyword">const</span> value = <span class="keyword">this</span>.vm.data[<span class="keyword">this</span>.exp] </span><br><span class="line">    Dep.target = <span class="literal">null</span> <span class="comment">// 释放自己</span></span><br><span class="line">    <span class="keyword">return</span> value</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      简易vue框架实现
找到了个简单代码实现了vue的少许基本功能的例子，有助于了解vue源码，加深对框架的理解。遇到问题也可以从原理方面分析。

实现目标
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


&lt;body&gt;
  &lt;div id=&quot;app&quot;&gt;
    &lt;h2&gt;{{t
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>字体加载优化</title>
    <link href="https://blog.dappwind.com/2018/08/21/"/>
    <id>https://blog.dappwind.com/2018/08/21/</id>
    <published>2018-08-21T09:59:33.000Z</published>
    <updated>2020-06-15T03:23:19.229Z</updated>
    
    <content type="html"><![CDATA[<h1 id="字体加载-技巧"><a href="#字体加载-技巧" class="headerlink" title="字体加载 技巧"></a>字体加载 技巧</h1><p>DIN-Bold  苹果有 安卓没有</p><p>如果只用外部字体  加载时间中 该字体空白 体验不好</p><h3 id="安卓："><a href="#安卓：" class="headerlink" title="安卓："></a>安卓：</h3><p>在页面中加入 preload</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"preload"</span> <span class="attr">href</span>=<span class="string">""</span> <span class="attr">as</span>=<span class="string">"font"</span> <span class="attr">type</span>=<span class="string">"font/woff"</span> <span class="attr">crossorigin</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="苹果："><a href="#苹果：" class="headerlink" title="苹果："></a>苹果：</h3><p> 不支持preload 但是苹果本来就有这个字体 如果定义外部字体为同样的名字的话 会覆盖本地字体。 所以给外部字体一个另外的名字，font-family  先写本地字体名字 再写外部字体。完美</p><figure class="highlight css"><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></pre></td><td class="code"><pre><span class="line">@<span class="keyword">font-face</span> &#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">'DIN-Bold-web'</span>;</span><br><span class="line">  <span class="attribute">src</span>: <span class="built_in">url</span>(<span class="string">'../common/fonts/DIN-Bold.woff'</span>) <span class="built_in">format</span>(<span class="string">'woff'</span>); <span class="comment">/* chrome, firefox */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@<span class="keyword">font-face</span> &#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">'DIN-Medium-web'</span>;</span><br><span class="line">  <span class="attribute">src</span>: <span class="built_in">url</span>(<span class="string">'../common/fonts/DIN-Medium.woff'</span>) <span class="built_in">format</span>(<span class="string">'woff'</span>); <span class="comment">/* chrome, firefox */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.DIN-Bold</span> &#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">'DIN-Bold'</span>, <span class="string">'DIN-Bold-web'</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.DIN-Medium</span> &#123;</span><br><span class="line">  <span class="attribute">font-family</span>: <span class="string">'DIN-Medium'</span>,<span class="string">'DIN-Medium-web'</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      字体加载 技巧
DIN-Bold 苹果有 安卓没有

如果只用外部字体 加载时间中 该字体空白 体验不好

安卓：
在页面中加入 preload

1


&lt;link rel=&quot;preload&quot; href=&quot;&quot; as=&quot;font&quot; type=&quot;font/woff&quot; crossorigin&gt;


苹果：
 不支持preload 但是苹果本来就有这个字体 如果定义外部字体为同样的名字的话 会覆盖本地字
    
    </summary>
    
    
  </entry>
  
</feed>
