Vue在chrome44偶现点击子元素事件无法冒泡

公司的一个项目大致是这样的:一个左侧列表,点击左侧列表的文章标题,右侧展开该文章对应的内容的。 现在的问题出现在极少部分客户有时左侧的标题,无法打开对应的右侧的内容,给人的改进就是‘卡’、点不动、点了没反应。 再大致介绍下项目环境: chrome 44(打包到用户客户端内) Vue 2.6.10 左侧列表布局 列表的每个绿色方框是一个vue组件,名叫ListItem,列表的组件叫List 代码类似这样 // List.vue <template> <div class="blue"> <list-item v-for="item in dataList" :data="item" @click="handleClick"> </div> </template> <script> export default { … methods: { handleClick() { … 请求文章内容相应的逻辑 } } } </script> // ListItem.vue <template> <div class="green" @click="onClick"> <div class="red circle"></div> <div class="red square"> Read more…

在iframe中使用富文本编辑器wangEditor

自己做的邮件项目里面需要使用到富文本编辑器,邮件内容说白了就是HTML代码。前任使用的是wangEditor,部分定制化需求就是直接改的源码。 最近发现有的用户的邮件内容加进去的很多css信息,比如 <link rel="stylesheet" href="style.css",用户可能是使用的模板加进去的,或者自己是个懂点代码的,因为我们默认是不会处理删除用户的东西,所以这些信息就带进去了,成为了邮件的一部分。 为了做样式的隔离,所以我们查看邮件内容时使用了iframe,这样邮件内容的样式和我们邮件系统的样式不会发生相互影响。但是前任在新建、编辑邮件时没有使用iframe,当时就很纳闷,为什么没有使用呢?编辑邮件肯定也要进行样式隔离的啊,之前就有时会碰到个别邮件,在点击回复时,整个邮件系统的部分样式都受到了影响,我当时第一时间看了下wangEdtor的官网,没有发现这样的配置,可能需要修改源码,比较麻烦,再加上抽不出时间、出现频率极低,就扔在一边没有管了。最近有时间准备解决下这个问题,再次看了wangEdtor的官网,还是没有发现这样的配置项,估计是没有人有这样的需求,或者作者不知道有这样的需求吧,看来只能自己动手了改了。 由于之前有做翻译的经验,深知多了个iframe会有小麻烦,要处理好window、document,因为这个是分iframe的。 首先想到的就是直接将内容区域的节点从div改成iframe,再改动_initDom,在里面进行一些是否使用iframe的判断, <div :class="'skin-mode-' + windowModel" style="display: flex;flex-direction: column;width: 100%;height:100%;"> <div :id="'toolbar'+unique" style="border-bottom: 1px solid #EBF2FA; font-size: 14px; padding:5px 10px;margin-bottom: 5px;flex: none; "> </div> <iframe :id="'body'+unique" style="height: auto;word-break: break-all;white-space: normal;flex: auto;display: flex;flex-direction: column;"></iframe> </div> const textContainerDom = $(textSelector)[0]; const isIframe = Object.prototype.toString.call(textContainerDom) === '[object Read more…

vue发送请求是应该在mounted还是在created生命周期

一个经常会被问到的问题: 为什么不在 created 里去发ajax?created 可是比 mounted 更早调用啊,更早调用意味着更早返回结果,那样性能不是更高? 首先,一个组件的 created 比 mounted 也早调用不了几微秒,性能没啥提高; 而且,等到异步渲染开启的时候,created 就可能被中途打断,中断之后渲染又要重做一遍,想一想,在 created 中做ajax调用,代码里看到只有调用一次,但是实际上可能调用 N 多次,这明显不合适。 相反,若把发ajax 放在 mounted,因为 mounted 在第二阶段,所以绝对不会多次重复调用,这才是ajax合适的位置

mysql 关闭3306端口对外开放服务

我们都知道mysql的默认端口就是3306,一般默认是对外开放的,也就是可以通过ip + 端口的访问直接远程访问,这样是有安全隐患,类似百度云观察这样的服务扫描到的话就会把这当成安全隐患提示站长。 对于只有一个服务器的用户,也就是只用使用单机版的mysql就行了。 我们怎么关闭3306端口对外开放呢? 修改/etc/my.cnf文件里面的[mysqld]的内容 最狠的办法 把skip-networking前面的#号去掉。 但是有时会影响其它软件的正常连接。(比如,grapqhl在本地127.0.0.1连接mysql都连接不上了) 折中的办法 注释掉skip-networking,但是加上一行bind-address = 127.0.0.1,这样就只能使用本地连接数据客。 这两种办法设置后重启mysqlservice mysqld restart,再进行在线扫端口号也会显示是关闭的。 一般个人单机使用MySQL就足够了,所以直接不让3306端口对外开放。

Javascript保留格式翻译选区内容及预览(完结)

前面的一篇文章Javascript保留格式翻译选区内容及预览(一),我们已经知道了,怎么获取选中的文字,然后交个接口返回,现在我们接着继续将这么讲翻译好的词语替换到原文的对应位置。 首先我们在选择文本并进行翻译时,我们的选区还在的,如果后续操作有存在丢掉选区的操作的话,我们需要先将选区暂存起来。 怎么保存选区 首先保存选区的前提是:选区还存在。 怎么说 ? 如果我们选中的词语是abc,如果abc都被替换了,之前保存的选区信息也无法使用了,或者不准确了,就不要使用了。 const ranges = []; for (let i = 0; i < window.getSelection().rangeCount; i++) { const range = window.getSelection().getRangeAt(i); ranges.push(range.cloneRange()); // clone后确保ranges可以用来重试 } 我们现在就把选区信息存储在`ranges`数组里面了 怎么恢复选区 既然提到保存选区,自然要知道怎么恢复选区了 // 恢复指定选区前移除所有可能存在选区 window.getSelection().removeAllRanges(); ranges.forEach(range => window.getSelection().addRange(range)); 替换原文 替换原文的前提是保持原文处于选中状态,也就是保留选区,现在这一步上面已经做到了,再加上第一篇提到的获取的选区的节点的最近一层的共同父节点commonAncestorContainer。现在我们正式开始进行替换了。 我们用两个方法完成替换,一个方法(traversalParent)是查找需要可能需要替换的dom节点。另一个方法(traversalReplace)进行替换。 寻找可能需要替换的dom节点 commonAncestorContainer里面是包含我们需要替换的dom节点的,但是里面可能还有很多无关节点,所以我们必须进行排除。 这就需要window.getSelection().containsNode方法,它能帮助我们判断某个节点是不是在选区中。 从MDN上看看它的api 我们可以判断指定节点是否全部或者部分包含在选区内。 如果dom全部内容都在选区是最简单的,直接全量替换了;如果只有不分在选区里面就麻烦点,比如原来的dom结构是<div>你们好</div>,但是用户只选中了“好”字,这时我们就不应该把“你们”给替换了。这部分工作就需要在我们的traversalReplace方法里面实现了。 window.getSelection().anchorNode和window.getSelection().focusNode分别能让我们知道选区的起始和结尾的节点。因为我们是人工选择文本节点,所以我们这里的起始、结尾节点极大概率是文本节点或者是包含文本的元素节点(只是我们看不见而已,这时就属于全部包含了,反而简单)。 文本节点的data属性会返回该文本的文字内容。 与anchorNode和focusNode对应的window.getSelection().anchorOffset和window.getSelection().focusOffset可以告知我们选择的文字在该文本节点的位置。 Read more…

从vuecli3学习webpack记录(二)webpack分析

上一篇里面讲到运行npm run serve时运行的是serveice.run(comand, args, rawArgv)并且提到它提示返回的是一个promise,所以后面还接着.catch(err => {…}); run方法里面是 return fn(args, rawArgv); 而fn就是registerCommand (name, opts, fn)的第三个参数,对于特定的npm run serve就是这样 // lib/commands/serve.js api.registerCommand('serve', { description: 'start development server', usage: 'vue-cli-service serve [options] [entry]', options: { '–open': `open browser on server start`, '–copy': `copy url to clipboard on server start`, '–mode': `specify env mode Read more…

Javascript保留格式翻译选区内容及预览(一)

目前市面上的不少翻译,一般场景比较简单,都是纯文本翻译(可能会包含换行\n之类的),但是最近遇到一个需求是要实现富文本里面的翻译,这里的翻译很大的概率会有格式,比如这种 我们需要带格式翻译,翻译成如下的效果 这种翻译跟大多数翻译一样,也需要一个预览的功能,便于用户预览翻译结果和微调翻译结果,然后点击替换就直接将翻译结果替换到富文本的内容里面,样式需要保留,比如上面的加粗。 做个类似选择功能,选区的都应该知道下列api Selection Range 常用的方法就是window.getSelection()以及window.getSelection().getRangeAt(),具体的用法大家可以参看MDN,这里主要说下里面的坑。 怎么保留样式 首先大家想到的是如果里面有复杂样式,比如选区的文字有加粗、颜色什么的,选区里面还有图片啊,更深一层讲的话可能就是有里面会还有class、style、img之类的,怎么过滤呢?其实我们这样看还只是看到的是视觉层面的东西,我们应该看里面都是一个个的element node:text,我们所谓的保留格式替换,其实也只是更换里面的文字内容信息即可,其它只有不是一股脑全换掉,就实现了我们的目标了。 怎么找到需要替换的文字 其实使用window.getSelection().toString()能获取到选择的文字,但是这种方式类似innerText获取的是当前节点及其字节的文字,所以会丢失层级关系。 根据我们的需求我们要使用的是遍历对应的dom节点的方法。常用的获取子节点的属性有两个: children返回的是HTMLCollection,不包含textNode childNodes返回的是NodeList,包含textNode 以下面的代码为例 <h2 class="lg_loginbox_title" id="logintitle">携程账号登录<a href="https://passport.ctrip.com/user/member/fastOrder" class="login_phone_number getOrder">手机号查单<i>></i></a></h2> 我们分别根据上述方式看看返回 很显然,这里我们需要使用childNodes。 既然遍历,就需要知道入口,我们从哪里开始遍历呢? 这就要用到前面说的Range了。 我们以这样的选区为例,我们模板就是获取到“账号登录”和“手机号”这几个文字并实现替换。 我们使用 widow.getSelection().getRangeAt(0).cloneContents(),目前我们都是只有一个选区,所以用getRangeAt(0)获取第一个选区的Range。 返回的fragment是就是我们选区里面的dom结构,并且class,style以及层级结构都保留了,这个API为我们做了很大贡献。 我们直接遍历它就能获取到选区的所有文字了,我们就按照深度遍历来将选区内的文字放到一个数组里面。我们就可以将数组[‘账号登录’, ‘手机号’]进行翻译得到[‘account login’, ‘phone number’]。 怎么将翻译好的文字替换到原来位置 这就需要我们找到选区在当前dom的位置了,这时我们还是看看Range了 window.getSelection.getRangeAt(0).commonAncestorContainer返回的是选区的节点的最近一层的共同父节点。 可以看到我们现在的选区其实都在h2里面,的确如此。 现在唯一的难点就是怎么找到文字的起始位置了,如果像我们上面的选区那样,用户选取的只是文字的一部分怎么办,比如“携程账号登录”只选取“账号登录”,“手机号查单”只选取“手机号”。 我们有两个办法可以做到 window.getSelection.getRangeAt(0).startContainer和window.getSelection.getRangeAt(0).startOffset可以获取到“账号登录”,因为“账号登录”就是从“携程账号登录”的第2位开始的(0为第一位,下同),就是说startContainer里面的第2位开始都是选区内容了。 window.getSelection.getRangeAt(0).endContainer和window.getSelection.getRangeAt(0).endOffset可以获取到“手机号”,因为“手机号”就是从“手机号查单”的第3位开始结束的,类似slice的前闭后开原则,就是说endContainer里面的第3位开始都是选区外了。 到此,是不是觉得这个需求有眉目了,能搞定了?剩下的我们下篇再讲。

支持取消单选组件vue版

原生的单选就是<input type="radio"/>,正常情况在name相同的单选之间只能选一个,如果只有一个单选框的情况下,一经选中是无法自己取消的,和checkbox不一样。但是有时我们的需求是需要有一个radio,并且还要支持能选中和取消的。 当radio的被选中的时候,其实它的checked属性是true,取消时为false。并且如果直接设置checked属性的值也能直接改变radio的选中与否。 我们也可以依此写成个简单的vue组件。 <template> <input type="radio" :id="label" ref="radio" @click="onClick" :value="innerChecked" /> </template> <script> export default { name: 'RadioToggle', props: { value: Boolean, label: String }, data() { return { innerChecked: false }; }, mounted() { this.innerChecked = this.value; }, methods: { onClick() { // 支持单个单选框选中/取消相互切换,选中态与isChecked保持一致 this.innerChecked = !this.innerChecked; }, Read more…

真的会用addEventListener吗,别掉到坑里了

大家用addEventListener肯定也听过removeAddeventListener,但是不少朋友使用有误区。 事件的添加和移除 // 示例1 // 添加事件 document.addEventListener('click', (e) => { console.log('冒泡 点击', e) }); // 移除事件 document.removeEventListener('click', (e) => { console.log('冒泡 点击', e) }); 上面的添加没有问题,但是移除没有效果了,就算移除的方法的代码跟添加一模一样也不行。 因为在使用removeEventListener的时候,要确保回调函数时同一个 // 示例2 function fn(e) { console.log('冒泡 点击', e) } // 添加事件 document.addEventListener('click', fn); // 移除事件 document.removeEventListener('click', fn); 向上面的方式使用就没有问题了。 但是我们经常在使用框架(以vue为例)的时候,有意无意的又会放上面示例1的错误了,我就经常看到这样的错误。 // 示例2 methods: { fn(e) Read more…

try catch的catch和promise reject的catch是一样的吗

我们先写两try catch,因为我们顺便测试catch来捕获try catch的异常。 function try2() { try { console.log("try2"); } catch (e) { console.log("err2", e); } } function try1() { try { console.log("try1"); aErr(); } catch (e) { console.log("err1", e); try2(); } } try1(); // 入口 里面会出错的就是aErr方法 // 示例1 function aErr() { c(); // 这里调用了一个不存在的方法 } 这个错误显而易见,打印结果也很简单 如果aErr里面是个promise呢 // 示例2 Read more…