先来看最终成果:css
操做逻辑为:html
...
弹出 context-menu
;context-menu
区域,隐藏 context-menu
;context-menu
中的任何一个选项,隐藏 context-menu
;项目是基于 vux
作的,本想着偷懒直接在 vux
库翻组件用,但看了一圈下来,竟然这么通用的组件在 vuex
中没有!接着又去翻开源的解决方案,看了几个库还算 ok,但此时前端小哥来了,说实现这个菜单不须要用到这么重的东西,直接写就好了。前端
当时个人脑海中在思考了把 context-menu
封装成一个 component
,经过数据配置的方式动态拓展菜单选项。但没想到前端小哥直接给我干了回来,不必进行封装,这个组件对页面依赖性太强,就算封装完了下次也不必定能直接用,PM 的思路又这么清奇。vue
因此,最后的作法就直接硬上了。git
该页面是一个通俗意义上的列表展现页,使用了 vux
的 swipeout
表单组件,给用户提供了侧滑操做,须要把原先写好的侧滑功能删除。github
在调整 UI 的过程当中我感到了 CSS 满满的恶意,固然说是这么说,但实际上仍是由于过久没有用而致使的不够熟悉。很是费劲的终于调整了好了新 UI,此时已通过去了整整一天了,很是怀念 autoLayout
。vuex
在正式开始写以前,上文已经说了我一直在翻开源库,主要是不懂得如何下手去写。距离上一次写 vue
已通过去快两个月了,并且也没搞清楚如何写一个组件,因此中间有一段时间浪费在了这上。最后的解决思路让我感到意外:网络
<div class="more-menu-wrapper">
<ul v-show="item.showOption">
<li>更换分类</li>
<li>向上移动</li>
<li>移至顶部</li>
<li>取消收藏</li>
</ul>
</div>
复制代码
没想到使用无序列表就能够完成了~在 iOS 中,我会在 UITableView
和 UIStackView
中纠结。固然只有这样是不行的,当又调整了 UI 后,发现 ...
和 context-menu
“融合”在了一块儿,没有设计图中的“悬浮”效果,最后的解决方法是:app
.more-wrapper {
/* ... */
position: absolute;
.more-menu-wrapper {
position: relative;
/* ... */
}
}
复制代码
当继续调整 CSS 时又发现 context-menu
的会被其父组件挡住,context-menu
的显示范围会限制于其父组件的显示高度,最后得知是 overflow
这个属性在最底层的父组件中设置了 overflow: hidden;
,删除掉,使其为默认的 visible
便可显示为 context-menu
高度溢出的效果。ide
UI 都调整完后开始绑定事件。由于只是改造 UI,并无涉及到多少的新逻辑,因此很快的就写出了如下代码:
<ul v-show="item.showOption">
<li @click="moveItem(item)">更换分类</li>
<li @click="moveUp(item)">向上移动</li>
<li @click="setTop(item)">移至顶部</li>
<li @click="deleteItem(item)">取消收藏</li>
</ul>
复制代码
context-menu
的显示依赖 v-show
,当页面首次拉取到网络数据时,data
中对每一个 listData
的 item
新增了 context-menu
显示隐藏的初始化标志位 item.showOption = false
,且在这四个入口方法中都控制了 item.showOption
的改变:
//...
moveUp(item) {
item.showOption = false;
// ...
}
//...
复制代码
刷新页面,很愉快的看到了 context-menu
的显示,但在点击菜单选项时没有任何反应!一开始觉得是标志位的问题,但看来看去没有任何问题。
原本想去找前端小哥看一眼,但一直不在工位上,最后问了下同组的前端实习生,他认为是 item.showOption
字段在数据更新时没有加上,致使后续直接读取时不存在。
但我其实一直纳闷若是 item.showOption
字段数据不存在的话,那第一次的页面渲染其实是有错误的。咱们两我的看了一会也没发现具体是哪有问题,最后只能四处寻找前端小哥,没想到他已经被封闭起来作商业化了......
前端小哥在文件中加上了 debugger
进行调试,发现进入到 moveUp
等一类事件时虽然 item.showOption
被修改为功了,一旦出去事件周期外,又被改回去了。
最后发现,问题出在被冒泡到了父组件中,调用了 ...
所绑定的 onMore
事件中,而在 onMore
事件中 item.showOption = true
,因此其实是执行了 context-menu
和 ...
的二者所绑定的事件。解决的方法是:
<ul v-show="item.showOption">
<li @click.stop="moveItem(item)">更换分类</li>
<li @click.stop="moveUp(item)">向上移动</li>
<li @click.stop="setTop(item)">移至顶部</li>
<li @click.stop="deleteItem(item)">取消收藏</li>
</ul>
复制代码
使用 @click.stop
来阻止冒泡事件。解决完问题后,前端小哥还好奇我作 iOS 怎么会不知道冒泡事件的问题,但实际上在 iOS 中跟前端的思路彻底是反过来的。iOS 的事件响应链是逐级传递到子组件中,也就是向下传递,而不是像前端中的向上传递。因此在遇到这个问题时也就彻底没有往冒泡的方面去思考。
context-menu
在 iOS 中,我会直接封装出一个带有 UIWindow
的组件。与 context-menu
有关的全部操做与主 window
没有任何关系,更别说事件穿透了。因此最终个人作法是多加了一个遮罩层,显示和隐藏的时机与 context-menu
的时机保持一致。
最后在我拿着最终的成果去找前端小哥复查时,他对这个作法不满意,仍是以为要使用 outside-click
的作法。也就是使用 js 中的事件代理,经过 e.targe
去判断。最后找到了可使用 v-outside-click
进行。v-outside-click
有两种引入的方式,为了简洁,我选择了“指令”的方式引入。
在使用 v-outside-click
这个库的过程当中遇到了一个比较大的问题。v-outside-click
此库给个人感受是用于单个组件,而不适用于多个组件。列表中的每个 cell
都须要带上一个单独的 context-menu
,若是给每个 context-menu
都绑上一个单独的 outside-click
事件,一旦用户的触摸范围不在 context-menu
中,则视图上的全部 context-menu
都会响应这个 outside-click
事件,列表数据一旦多起来,事件响应次数将线性增加。
这个问题跟前端小哥说事后,他说问题不大,那就这样吧~接下来的问题就到了怎么在 outside-click
事件中标识出是哪一个 context-menu
须要隐藏呢?刚开始就按照了以往的套路,直接使用了以下所示的方式:
<div v-click-outside="onClickOutside(item)">
<!-- ... -->
</div>
复制代码
而后开心看到了报错 Binding value must be a function or an object
。提示须要传入一个方法?!翻了源码后发现了这么一段:
function processDirectiveArguments(bindingValue) {
const isFunction = typeof bindingValue === 'function'
if (!isFunction && typeof bindingValue !== 'object') {
throw new Error('v-click-outside: Binding value must be a function or an object')
}
// ...
}
复制代码
回过头去看以前写的代码,没有问题啊!思来想去仍是没弄明白,又去找了前端小哥请求帮忙,通过了一番折腾了,他的结论是这个库应该是有问题的。最后采起的解决方法是:
<div v-click-outside="onClickOutside">
<p>…</p>
<!-- 重点 -->
<div :id="item.metricId" v-show="item.showOption">
<ul>
<li>更换分类</li>
<!-- ... -->
</ul>
</div>
</div>
复制代码
onClickOutside (event, el) {
let queryInstance = el.querySelector('.more-menu-wrapper')
if (queryInstance) {
let metricId = el.querySelector('.more-menu-wrapper').id;
if (metricId != "") {
this.listData.some((item) => {
if (item.metricId == metricId) {
item.showOption = false;
return true;
}
});
}
}
}
复制代码
经过设置 context-menu
的 id
做为标识,而后在 v-outside-click
的指令方法中获取 id
,经过这个 id
去数据源中找到对应的 item
,从而设置 item.showOption = false
来隐藏 context-menu
。
这算是转大前端完成的第一个功能吧,由于不熟悉致使中间出现了一些好玩的事情。客户端和前端的开发流程说大也不大,但要是说没有是绝对不可能的。在一些小的问题上,没有踩过坑或者没有大佬带一带,真的会爬不起来或者就弃坑了,说到底其实仍是须要多加学习啊!