前言
今年五月份开始,公司开始构建一套新的项目,用于企业智能外呼的配置。该项目我承担的角色是:项目的搭建、核心功能的实现以及疑难问题的解决,在一步步完成的过程中,学习并实践了非常多,故写此文总结。
话术树的实现
该项目最核心的功能是就实现话术树(该功能已经封装成组件)。什么叫话术树,就是企业运营人员可以配置机器人给目标客户打电话,电话的轮回过程中机器人可以根据匹配的语句对应回应怎么样的话术。实现话术树要解决的问题是:
- 实现可操作的树结构
- XML与json的相互转换
- 树节点的处理
实现可操作的树结构
树结构基于D3实现,可编辑的节点使用<foreignObject>
,当然,这也导致该功能在ie浏览器不可使用。
要实现的功能大致是:
- 自定义节点样式
- 右键菜单
- 树的拖动(drag)
- 树的缩放(scale)
关于树结构的实现,我大致参考了以下demo的代码:
- Collapsible tree diagram in v4
- D3.js Drag and Drop, Zoomable, Panning, Collapsible Tree with auto-sizing
然后把他们转化为vue的思想,分成了Tree
、Link
、Node
三个组件,Node
组件里包含ForeignObjectElement
,用于自定义节点,他们的层级关系如下图所示:
自定义节点样式
在实现组件的过程中,总结了以下几点知识点:
作用域插槽
一般自定义组件内容,我们会使用插槽
slot
。以
Modal.vue
组件为例,我们要在覆盖默认在Modal.vue
的内容,是在组件的内容部分包裹<slot>
标签,这样我们// Modal.vue <template> <div class="modal"> <div class="modal-content"> <div class="modal-body"> <slot>{{content}}</slot> </div> </div> </div> </template> // 调用 <Modal v-model="value"> <p>我是弹框内容<p> </Modal>
但是在本组件中,节点的数目不止一个,并且我们需要根据节点的属性值做出相对应的渲染,也就是说,我们希望提供的组件带有一个可从子组件获取数据的可复用的插槽,这个时候就要用到作用域插槽
<Tree> <template slot-scope="scope"> // 根据scope节点渲染 </template> </Tree>
provide / inject
但是scope访问的是Tree的
props
,而我们需要的属性在ForeignObjectElement
上,想要获取ForeignObjectElement
上的属性,就要用到provide-inject这两个Api。// Tree.vue export default { name: 'tree', provide () { return { $$tree: this } }, // ... } // ForeignObjectElement.js export default { name: 'ForeignObjectElement', inject: ['$$tree'], // ... }
这样我们就能获取
Tree
组件实例,把node节点上的属性传给Tree
实例上的方法。渲染函数
把node节点上的属性传给
Tree
实例上的方法,用的就是渲染函数,然而 render函数的语法 对树的自定义节点编写并不友好,你需要不停调用 createElement 函数并层层嵌套。但是vue提供了babel插件支持JSX 语法。我们在作用域插槽上得到了树节点的一系列属性,要在界面中展示拥有该属性的图标,jsx语法让我们回到更接近于模板的语法上。
// 属性节点的展示 <p class="ivrnode-props"> { icons.map(d => { if (d.value) { return (<span class={d.name} title={d.title}></span>) } }) } </p>
右键菜单
右键菜单直接使用了插件v-contextmenu
要注意的就是菜单栏出现的位置:第一考虑兼容性问题;第二考虑滚动情况的位置;第三考虑边界情况。
树的拖动(drag)
树的拖动监听zoom
方法,控制 树的 svg transform:translate
的值,可以参考 demo相对应的代码
树的缩放(scale)
树的缩放就是控制 树的 svg transform:scale
的值,由于项目要求滚动轴控制(类似地图缩放),那么就存在一个精度缺失的问题,解决的办法是引用了库number-precision。