如何写一个拖拽日历组件(附源码)

作者简介 Kid 蚂蚁金服·数据体验技术团队

本文会介绍如何写一个可拖拽日历组件,偏重点在于日历组件的功能挖掘以及对于开发过程的一些思考,编码部分会介绍核心部分的实现。代码在最后也会放出来给大家。

效果演示

先看下项目最后的实现效果:

前期调研

要做一个可拖拽日历组件,先得去看一圈市场上已有的日历组件做成什么样了。主要是为了收集功能以及避免重复造轮子。我调研了google日历,tower,fullcalendar,teambition,webmail等的日历控件。其中google日历和fullcalendar算是使用感受比较好的了,以下是他们的使用动图:

google日历

fullcalendar

功能对比

以下是功能细节的对比列表:

拖拽已有事件
(拖拽默认起始位置)
拖拽已有事件拖拽中效果
(产生遮挡)
左右拖拽事件
(只实现了单方向,并且产生遮挡)
拖拽空白区域新建
事件智能布局
(跨周后事件布局乱了)

从表格的对比可以看到,这几家实现的还算不错的日历组件功能都没有实现全,而且在一些细节上都没做好。能找到的开源的只有fullcalendar,不过他是jquery版本的,而且功能实现的并不好,细节上也有不少错误,所以就打算自己写一个了(终于找到重复造轮子的理由了~)。

功能

根据之前的调研,一个较好的可拖拽日历控件需要实现的功能:

  • 拖拽已有事件
  • 双击添加事件
  • 左右拖拽事件
  • 拖拽空白区域新建事件

除了功能之外,还有用户可用性上的细节打磨:

  • 拖拽已有事件的阴影效果
  • 拖拽已有事件拖拽中隐藏
  • 左右拖拽的预览以及阴影效果
  • 事件智能布局,包括堆叠和换行后的冗余清理
  • 拖拽态的处理(偏移量计算以及dragLayer的重写)

编码

需求调研完了进入到编码过程,项目主要基于react和react-dnd。编码的整体步骤包括:

  • 绘制日历
  • 根据事件列表绘制事件
  • 事件布局整理,堆叠效果和换行清理
  • 让事件可拖动
  • 让事件可左右拖动
  • 让空白区域可拖动勾选
  • 拖拽阴影处理 细讲的话文章太长,大家看着累,我讲一下关键实现点。

可拖拽区域

主要是用了react-dnd的拖拽能力,图里的4种颜色的圈分别对应了4种可拖拽组件的source。

  • 紫色是事件本身
  • 红色是向左拖拽区域
  • 黑色是向右拖拽区域
  • 绿色是拖拽空白的区域(这个设计最有意思,用拖拽的行为来模拟选中~)

为这4种source分别处理好对应的拖拽事件。为每个source定义好各自的hover事件可以处理不同的阴影效果。定义好layer可以处理拖拽时鼠标的预览效果。

事件智能布局

事件分类

就是把一天的事件分为如图所示的5种:

  • 1是有前一天,有后一天
  • 2是无前一天,有后一天
  • 3是有前一天,无后一天
  • 4是无前一天,无后一天
  • 5是空白的填充事件

渲染某一天的时候,先渲染1和3这种有前一天的,因为他们有个稳定的index,然后按照优先级渲染事件2,然后是4,填不满空白的渲染事件5。然后将1和2的index传到下一天。这样就能较好的布局了。

新周事件清理

如图,检测到新一周开始的时候,我会先清理中间的空白。然后再根据优先级排序事件。

整个开心的代码编码过程结束后,居然发现自己内心有些拒绝这份代码..为啥写完代码,调试完成后,自己不愿意再回头看了呢。哦,是因为真的写的不好看啊!不好看换句话说就是代码的内在质量差。

质量

软件质量可以分为外在的和内在的。外在质量包括:

  • 可用性
  • 正确性
  • 健壮性
  • ....

外在质量是用户关心的唯一软件特性,我上面一直追求和打磨的其实都属于软件的外在质量。用户是爽的~

可我内心有些拒绝这份刚写的代码的原因是因为软件的内在质量出了问题,内在质量包括:

  • 可读性(可能是最重要的了)
  • 可维护性
  • 可接入性(现在支持自定义form,感觉还是不错的~)
  • ....

写着写着发现代码不好理解了,耐下心细看一下发现:

  • 代码有冗余
  • 子程序命名没有起好
  • 代码结构混乱
  • 瞎定义css名
  • ....

接着干嘛,先重构咯~

  • 整理代码结构,抽抽抽,让整体流程清晰
  • 重新思考子程序命名,写注释
  • 思考对外提供的接口,加强可接入性
  • .....

重构完成一波后思考为什么内在质量会出现问题呢?

质量上的追求是无穷尽的,无论是内在的还是外在的。很多技术都可以提升质量:补充测试用例,良好的编程风格,合理的注释,分层抽象,合理的接口...让所有的特性都表现的尽善尽美绝无可能。根据一组互相竞争的目标找出一套解决方案,正是这种情况让软件成为一个真正的工程学科

设计

为什么质量会差呢?当然是因为设计的有问题,才会在开发中发现设计不符合而不断调整设计,导致破坏了软件中非常重要的“系统完整性”(人月神话相当推崇的重点)。最终就导致了质量差。

那么为什么没有好好设计呢?我估计是因为没有抵挡住“赶紧开始编码”的诱惑! 在思考了如何拖拽已有事件之后我就迫不及待的开始编码。没有进行更为详细的设计。其实这个阶段我在大脑里已经隔绝了后续的需求了(为了智力上可管理采取的自我保护吧~)。而在实现完拖拽已有事件之后,我开始思考如何拖拽空白区域新建,发现已有的设计需要调整。然后在想到了一个还不错的点子之后就又开始了编码。设计不完备的循环导致了质量的下降。

其实软件的产生与现实中的食物链一样,需求->设计->编码。一环套一环,越靠近上游出了问题影响面越大。归纳就是我每次的设计都是基于并不完整的需求来做的。

应该怎么做呢,应该在最开始的时候尽可能的设计全才行。应该加大在初期投入的设计时间。

迭代式开发

可能有朋友看到这里感觉,不对啊,设计本来就是“险恶的问题”(也就是必须被解决或者部分解决才能定义的问题)。所以开发过程采用迭代式开发没问题啊。迭代式的好处在于有利于规避风险。不断小成本的尝试来降低失败的风险。所以本来迭代式的开发就必须配备不断的重构来保证质量的。

对于这个问题。我觉得应该看项目的规模。看我们脑海中可控的复杂度。可能更好的方式是进行原型的开发进行试验,试验发现可能之后立刻重新整体设计,然后再编码,这样的成本会更小些。

就是在初次迭代进行了一些难点的验证之后,进行整体设计,这样可以减少迭代式开发后必不可少的重构环节,减少整体的开发成本,也提高了项目的质量。

总结

本文分享了可拖拽日历组件的实现过程,编码细节以及对于整体开发的思考。之所以会探讨对于开发的思考是因为各种方法论都被用于大小不同的项目,对于小项目,方法论的应用显得很不经意且趋于本能。如果我们有选择的使用一些方法论的话是能够减少开发时间以及提升产品质量的。最后附上代码的传送门,具体的接入使用可以看下项目的README,还是有相当多缺陷的,欢迎提PR~

对我们团队感兴趣的可以关注专栏,关注github或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、基础框架:基础框架里面有什么东西,为什么有他,比如说多环境适配,因为我们整套数据平台的解决方案是对外输出的,有公有云环境,有专有云环境,这些环境底下的基础设施都不一样,比如说包括租户和账户体系,权限体系,流程体系,审批流这类东西,所以正是通过基础框架搭我们底层的环境。最主要目的其实是提供一些我们上层数据应用的通用能力以及把底层的数据环境的差异给屏蔽掉。 2、核心能力: ① 数据安全:数据安全就会涉及到数据资产的分类、分级。不同类别的资产,他的安全等级是不一样的,他在安全里面需要有权限的话,他的审批策略是不一样的,这是数据安全这一块,可能还涉及一些比如脱敏,我们消费端接触到这些数据怎么脱敏; ② 隐私保护:隐私保护更偏重,比如说隐私保护还有一个叫法是数据安全、数据合规,我们想要做什么事情,就是我们要去透明化的看到各个公司数据流通,比如有哪些数据,这些数据的安全等级是什么样的,涉及到用户哪些数据; ③ 数据质量:主要是在我们数据研发过程当中,数据周期从发布到线上调度,调度完了怎么去做数据质量的监测,检测完了以后,比如说我们做离线调度的时候最重要的一个就是数据产出时效,所以有一个基线。这都是怎么去保障我们任务的基线; ④ 元数据中心:元数据中心大家都知道,因为我们下面有各种各样不同的引擎,有Spark,有ODPS,有MySQL这些东西,怎么去把它当中的数据统一的元数据中心; ⑤ 数据治理:数据治理的逻辑就是配合数据质量把我们现有的数据给盘清楚。 3、数据引擎 ① 任务执行与调度引擎:我们在做ETL的时候大多数都是这种任务执行与调度; ② 数据科学引擎:数据科学引擎主要是做分析,做业务洞察这一类,今天的数据业务平台可能更多的就是依赖于数据科学引擎,后面会详细介绍; ③ 决策服务引擎:决策引擎比如说给大家举一个场景,芝麻分大家都知道,那首先假如我有一个业务在线上,在线上做策略的时候,或者给大家看不同的页面的时候,不同的芝麻分的等级看到的页面或者等级是不一样的,这种东西是需要数据决策的,或者直白的来说,是需要这个人的芝麻分,这个通过统计数据服务会去配一个决策规则,相当于这里的决策引擎里面支持一种决策的DSL配置,简单来讲就是if……else……,if…else……,能够配置这样一套规则后,给在线业务场景提供服务,这是决策服务引擎。整个数据内核就这么多东西。
好的,我可以帮你解答这个问题。 首先,我们需要安装 `vue-draggable-resizable` 这个库,它可以帮助我们实现拖拽和调整大小的功能。可以使用以下命令进行安装: ``` npm install vue-draggable-resizable ``` 接下来,我们可以创建一个名为 `DraggableDialog.vue` 的组件。代码如下: ```html <template> <vue-draggable-resizable :w="width" :h="height" :x="x" :y="y"> <div class="dialog-header" @mousedown="onMouseDown"> <slot name="header">Header</slot> </div> <div class="dialog-body"> <slot name="body">Body</slot> </div> </vue-draggable-resizable> </template> <script> import VueDraggableResizable from 'vue-draggable-resizable'; export default { components: { VueDraggableResizable, }, props: { width: { type: Number, default: 400, }, height: { type: Number, default: 300, }, x: { type: Number, default: 0, }, y: { type: Number, default: 0, }, }, methods: { onMouseDown(event) { const offsetX = event.clientX - event.target.offsetLeft; const offsetY = event.clientY - event.target.offsetTop; const onMouseMove = (event) => { this.x = event.clientX - offsetX; this.y = event.clientY - offsetY; }; const onMouseUp = () => { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }, }, }; </script> <style scoped> .dialog-header { background-color: #eee; padding: 8px; cursor: move; } .dialog-body { padding: 8px; } </style> ``` 在这个组件中,我们使用了 `vue-draggable-resizable` 来实现拖拽和调整大小的功能,同时也添加了自定义的样式和鼠标事件来实现拖拽的效果。 你可以在父组件中使用这个组件,并传递一些 props 来设置弹窗的大小和位置。例如: ```html <template> <div> <button @click="showDialog = true">Show Dialog</button> <draggable-dialog v-if="showDialog" :x="dialogX" :y="dialogY" :width="dialogWidth" :height="dialogHeight"> <template v-slot:header> Custom Header </template> <template v-slot:body> Custom Body </template> </draggable-dialog> </div> </template> <script> import DraggableDialog from './DraggableDialog.vue'; export default { components: { DraggableDialog, }, data() { return { showDialog: false, dialogX: 100, dialogY: 100, dialogWidth: 400, dialogHeight: 300, }; }, }; </script> ``` 在这个例子中,我们使用了 `v-if` 来控制弹窗是否显示,同时传递了一些 props 来设置弹窗的大小和位置,还使用了 slot 来自定义弹窗的头部和内容部分。 这样,一个简单的拖拽弹窗组件就完成了。希望能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值