每周时间表编辑器

我写这封信是因为我一年中第三次遇到这项任务。 每次,它都始于一个令人惊奇的创意解决方案,变得更容易,最后是我将要讨论的系统。

目标是创建和维护每周时间表,例如学校课程时间表或医生和官员时间表。 有一组插槽,每个插槽是每周计划中的一个位置,带有各种其他参数,例如机柜号,员工姓名。 需要构建一个具有完整历史记录的灵活系统,该系统可以解决以下问题:从暑假开始创建不同的时间表,在接下来的三周内更换老师,由于假期而将时间表从星期五移至星期六。

我将写出他们通常会遇到的问题以及如何解决的问题,我将解决绘制条带的问题,然后我将在node / sequelize上给出一个简单后端的示例,并在vue / vuex / vuetify / nuxt上给出一个简单的前端,在这里您可以用鼠标将其全部拖动并查看它是如何工作的。

这些代码发布在github上 ,并在此处部署。



颗粒状变化


数据库中以某种方式提供了一个插槽。 需要编辑。 因此,您需要绘制一些带有字段的表格,并在“保存”按钮下方。 毕竟,通常情况都是这样安排的。 但是,不是这种情况。 考虑以下形式:


保存时,插槽的所有数据都会更新,历史记录将丢失。 让我们尝试添加这样的元素:


再来一次。 例如,在6月4日,星期一,记录了从第一个办公室到第二个办公室一天的班级转移。 然后出现了新的需求-从5月28日起,该课程将始终在20:00而不是19:00开始。 我们打开表格,更改时间,指示从28日开始的日期,直到永远,然后...所有字段以及机柜号进入服务器。 6月4日的临时更改已被覆盖。 使用这种形式,因为所有字段都已发送,所以无法确定用户要更改间隔的字段。

其思想是,每个规则以其自己的间隔独立于其他规则而变化。 插槽由一组一维参数定义,每个参数都有由一组规则定义的更​​改历史记录。 每个规则都包含一个值,开始日期和结束日期。 由于这是每周日历,因此日期足以表示最多一周,即YYYYWW。


现在看来,编辑广告位非常复杂-要更改多个字段,您需要选择每个字段,打开表单,输入一个值和一个间隔。 但是,在实践中,事实证明,改变几个领域是一种罕见的情况。 一次更新多个插槽的频率更高。 例如,要平息因病缺席的教师,您需要选择其所有职位,将工作人员分配状态设为病假,然后为相同的职位选择替代老师。 在这种情况下,仅2个动作而不是n个插槽的n个动作,就好像它们是通过传统形式指定的一样。 在我当前正在使用的StarBright.com系统上,它看起来像这样:


画条的任务


考虑一条由涂有不同颜色的单元格组成的条。 每个单元格是一个星期,每种颜色是一种含义。 一种新的颜色出现了,并且间隔了一段时间,他们需要重新涂上新的颜色。 在数据级别,这意味着您需要删除完全重叠的间隔,更改部分重叠的间隔,添加新的间隔,并将相邻的一种颜色间隔合并为一个。 最终结果应包含不重叠的间隔。


结果:[{delete,id:2},{update,id:1,数据:{to:5}},{update,id:3,数据:{from:16}},{insert,data:{from :6,至:15,值:星期三}}]

这是一项简单的任务,但在这里很容易忽略。 是一个单独的存储库,其中包含解决方案和测试。 http://timeblock-rules.rag.lt-在这里您可以检查它的工作方式并使用阴影播放。

后端


规则不重叠,因此最简单的从规则中选择select * from <=:week and(to为null或to> =:week)足以选择指定星期的必要规则。 是节点/ sequelize上的一个简单示例后端。 它使用了c promise和async / await的组合样式,您可以在我的另一篇文章中了解到。

这是为指定星期选择规则的操作:
routes.get('/timeblocks', async (req, res) => { try { ... validation ... await Rule .findAll({ where: { from: {$or: [{$lte: req.query.week}, null]}, to: {$or: [{$gte: req.query.week}, null]} } }) .then( sendSuccess(res, 'Calendar data extracted.'), throwError(500, 'sequelize error') ) } catch (error) { catchError(res, error) } }) 


这是更改规则集的PATCH:
 routes.patch('/timeblocks/:id(\\d+)', async (req, res) => { try { ... validation ... const initialRules = await Rule .findAll({ where: { timeblock_id: req.params.id, type: {$in: req.params.rules.map(rule => rule.type)} } }).catch(throwError(500, 'sequelize error')) const promises = [] req.params.rules.forEach(rule => { // This function defined in stripe coloring repo, https://github.com/Kasheftin/timeblock-rules/blob/master/src/fn/rules.js; const actions = processNewRule(rule, initialRules[rule.type] || []) actions.forEach(action => { if (action.type === 'delete') { promises.push(Rule.destroy({where: {id: action.id}})) } else if (action.type === 'update') { promises.push(Rule.update(action.data, {where: {id: action.id}})) } else if (action.type === 'insert') { promises.push(Rule.build({...action.data, timeblock_id: rule.timeblock_id, type: rule.type}).save()) } }) }) Promise.all(promises).then( result => sendSuccess(res, 'Timeblock rules updated.')() ) } catch (error) { catchError(res, error) } }) 


这是后端最困难的意识形态部分,其余的甚至更简单。

问题是如何删除插槽。 在这种情况下,将存储完整的历史记录,而不会删除任何内容。 有一个状态字段,可以打开,暂时关闭和关闭。 访客看到活动的插槽,而暂时不活动,在通常情况下,管理员通常会写评论为什么没有活动。 随着时间的流逝,有很多封闭的时间段,为了简化这种情况,引入另一个属性(例如学年)并在编辑时间段时仅显示当前的学年很有用。

前端


代码在此存储库中 ,它是nuxt上一个简单的一页站点。 实际上,ssr会遇到一些麻烦(例如, 详细分析了如何在nuxt上编写身份验证),但是简单的应用程序可以很快写在上面。

这是单个页面的代码:
 export default { components: {...}, fetch ({app, route, redirect, store}) { if (!route.query.week) { const newRoute = app.router.resolve({query: {...route.query, week: moment().format('YYYYWW')}}, route) return redirect(newRoute.href) } return Promise.resolve() .then(() => store.dispatch('calendar/set', {week: route.query.week})) .then(() => store.dispatch('calendar/fetch')) }, computed: { week () { return this.$store.state.calendar.week } }, watch: { week (week) { this.$router.push({ query: { ...this.$route.query, week } }) this.$store.dispatch('calendar/fetch') } } } 


fetch方法可在服务器和客户端上使用,重定向到当前星期并请求日历。 当星期更改时,将重新请求数据。

插槽重叠怎么办? 答案取决于业务逻辑,例如,您可能需要禁止重叠的服务器验证。 在这种情况下,允许覆盖,并且为了获得漂亮的图片,将这些狭缝绘制为彼此相邻的一半宽度。 添加布局并获得以下外观:



其他所有内容都是纯JavaScript,没有特殊提示。 通过在块上单击鼠标,开始拖放。 mousemove和mouseup事件挂在整个窗口上。 拖放从200毫秒的延迟开始,以区分拖动和点击。 跟踪下落的容器的参数是预先计算的,因为getBoundingClientRect的操作过于繁琐,无法每次鼠标移动。 我必须做两种形式-一种用于创建(从本周开始一次设置所有规则),另一种用于对广告位进行细化更改。

http://calendar.rag.lt-在这里您可以检查所有工作方式。

链接到文章


Source: https://habr.com/ru/post/zh-CN412665/


All Articles