为采用Bootstrap框架的后台管理系统制作一张点位排期的表格,基于jQuery封装了一个名为Calendar的插件,总共有3次大的迭代,代码从最初的200多行飙升至1000多行,包括一些工具函数,例如日期格式化、字符串模板、类型判断等。在封装插件前,先用CSS和HTML编写了静态的排期表,在此基础上通过JavaScript改成了动态渲染。
一、点位排期
刚开始接到的需求比较简单,如下图所示。
最上面的是日期过滤,可选择上一日或下一日。每页表格显示一个月的数据。第一行是日(天数),红底表示休息天。第一列是版位名称,点击版位名称可显示浮层,包含一张说明图。在单元格中可填入文字、图标或链接,其中白底是可点击的空闲排期,点击后显示一个勾,再点击就会取消;灰底是预排期,蓝底是下单排期,两者都不可点击。
1)服务器数据
服务器返回的数据是前后端协商后的结构,字段比较多,包含多个数组,例如当月天数、休息天列表、已选中列表、版位信息等,具体如下所示,其中class属性是样式编号,原先是数字,在迭代的过程中改成了数组。
<?php $json = [ 'code'=>200, 'msg'=>'操作成功', 'data'=>[ 'prev'=>'2016-09', //上一日 'next'=>'2016-11', //下一日 //当月天数 'month'=>[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31], 'holiday'=>[1,2,8,9,15,16,22,23,29,30], //休息天列表 'check' => [ //已选中列表 '2016-09'=>[ //以日期为key 1 => [1,2,3,4,5,6], //以版位ID为key,天数列表为value 2 => [1,2,5,8] ], '2016-11'=>[ 1 => [3,4,5,6], 2 => [5,8] ] ], 'list' =>[ [ 'label'=>[ //版位信息 'key'=>1, //ID 'img'=>'image/demo.png', //可弹出的说明图 'name'=>'首页文字链接' //版位名称 ], 'check'=>[ [ 'day'=>1, //天数 'class'=>[4], //空闲排期 已选中 带跳转 'url'=>'http://www.pwstrick.com', 'price'=>100 //价格 ], [ 'day'=>2, 'class'=>[4], //空闲排期 已选中 'price'=>100 ], [ 'logo'=>'image/logo.gif', 'day'=>3, 'class'=>[5], //空闲排期 已选中 带跳转和图标 'url'=>'http://www.pwstrick.com', 'price'=>100 ], [ 'logo'=>'image/logo.gif', 'day'=>4, 'class'=>[3], //空闲排期 'price'=>100 ], [ 'day'=>5, 'class'=>[2], //预排期 'price'=>100 ], [ 'day'=>6, 'class'=>[1], //下单排期 'price'=>100 ], .... ] ] ] ] ]; echo json_encode($json);
在插件中,定义了样式表字典,其含义与服务器返回的class属性一样,如下所列。
this.enums = { //样式表字典 1: "primary", //蓝色 完成状态 2: "info", //灰色 审核状态 3: "calendar-check", //白色 可打勾 4: "checked", //已打勾 5: "info-blue" //跳转 };
2)排期渲染
在初始化Calendar插件时,既可以传递自定义的参数,也可以传递“data-”为前缀的自定义属性,例如异步请求地址data-ajax、当前日期data-cur、控制单元格能否点击的data-disabled等属性。下面是Calendar插件的HTML结构和模板,以及带额外参数的初始化代码。
<script id="template" type="x-tmpl-mustache"> {{#data}} <thead> <tr> <th></th> {{#month}} <th>{{.}}</th> {{/month}} </tr> </thead> <tbody> {{#list}} <tr> <td data-key="{{label.key}}" data-img="{{&label.img}}">{{label.name}}</td> {{#check}} <td data-day="{{day}}" class="{{class_name}}" data-remark="{{&remark_name}}" data-dialog="{{dialog}}" data-id="{{id}}" data-width="{{width}}" data-height="{{height}}" data-price="{{price}}"> {{&img_str}} </td> {{/check}} </tr> {{/list}} </tbody> {{/data}} </script> <div id="calendar1" class="calendar-continer" data-loading="img/ajax-loader.gif" data-ajax="ajax/calendar.php" data-cur="2016-10"> <div class="calendar-header"> <div class="calendar-date"> <i class="glyphicon glyphicon-chevron-left" data-date="2016-09"></i> <span name="cur-date">2016-10</span> <i class="glyphicon glyphicon-chevron-right" data-date="2016-11"></i> </div> </div> <div class="calendar-content"> <table class="table table-bordered"></table> </div> </div> <script> var calendar1 = new Calendar('calendar1', { param: { //额外参数 用于ajax请求 begin: '2017-01-01', end: '2017-11-01' } }); </script>
Calendar插件包含一个空的table元素,通过mustcache.js模板渲染。在refresh()方法中完成了渲染逻辑,并且将选中的单元格信息缓存到本地。
当单元格包含备注信息时,会配合tooltip插件,添加提示的效果,如下图所示。
3)缓存数据
缓存的数据会在initChecked()方法中初始化,其JSON结构如下所示,对选中和未选中的单元格做单独记录。
{ "2016-10": { //日期(年月) "1": { //版位ID checked: [ //选中列表 day表示年月日中的日,即天数 { day: 1, class: [4, 3], url: "http://www.pwstrick.com", css: 4 }, { day: 2, class: [4, 3], css: 4 } ], unchecked: [3] //移除列表 } } };
每次点击与取消都会触发缓存的修改,在私有的_set()方法中处理缓存数据。
4)优化
在使用一段时间后,运营觉得第一列和第一行如果固定住,对他们的工作能更加的友好,于是就加了这个特效,具体细节可参考之前的《表格花式效果》一文。
二、版位互斥
当一个版位和其它多个版位发生互斥时,如果这个版位是预留或下单状态,那么其它版位就无法点击,反之亦然。在服务器返回的数据中新增exclusions属性,如下所示。
<?php $json = [ 'code'=>200, 'msg'=>'操作成功', 'data'=>[ 'exclusions'=>[ 5 => [3,4] //版位ID ] ] ];
为了将插件修改最小化,互斥的逻辑封装到一个checkExclusions()函数中,需要判断的位置就调用该函数。
三、排期类型
原先只有一个点位排期,后面新增了带购买、带点击数和素材三种排期。一开始没有想到会有多种排期类型,这次迭代修改很多,几乎增加了一倍的代码,并且服务器返回的数据结构也做了调整。
1)初始化
在插件内部抽象出了_initWithType()方法,专门用于初始化不同类型的排期表,在初始化时还得传入排期类型,不传就渲染为普通的点位排期。
Calendar.prototype._initWithType = function(opts) { opts = opts || {}; var _this = this; switch (opts.type) { case 'buy': this.checked = [4, 6]; //选中的样式 this.enums = { //样式表字典 1: 'primary', 2: 'info', 3: 'calendar-check', //可打勾 4: 'buy', //购买 已打勾 5: 'info-blue', 6: 'send' //配送 }; this._get = function() { var type = _this.type; //排期类型 var data = {}; data[type] = {}; for(var key in _this.cache) { for(var key2 in _this.cache[key]) { data[type][key2] = data[type][key2] || []; //每一行的key $.each(_this.cache[key][key2]['checked'], function(key3, value) { //传对象到服务器 日期和样式 data[type][key2].push({date:key+'-'+value.day, css:value.css}); }); } } return data; }; cellDbClick(_this); break; default: this.checked = 4; //选中的样式 this.enums = { //样式表字典 1: "primary", //蓝色 完成状态 2: "info", //灰色 审核状态 3: "calendar-check", //白色 可打勾 4: "checked", //已打勾 5: "info-blue" //跳转 }; this._get = function() { var type = _this.type; //排期类型 var data = {}; data[type] = {}; for (var key in _this.cache) { //遍历日期,例如2016-10、2016-09 for (var key2 in _this.cache[key]) { //先取第一列的版位ID data[type][key2] = data[type][key2] || []; //将表格类型下的版位ID初始化为数组 $.each(_this.cache[key][key2]["checked"], function(key3, value) { //提取每月打勾的日期 data[type][key2].push(key + "-" + value.day); //只传日期 }); } } return data; }; cellClick(_this); break; } };
不同的排期表,其样式表字典也是不同的。this._get()是内部用于获取缓存数据的方法,由于每种类型回传给服务器的数据格式不同,因此需要单独处理,但名称可以统一,便于调用。cellDbClick()和cellClick()都是用于绑定单元格点击事件的方法,只是两者内部的处理逻辑不同。
2)带点击数的排期表
此类排期表需要与artDialog弹框插件配合使用,因为在点击单元格时需要出现两张表单,下图是其中的一张。
初始化弹框和表单提交的逻辑都封装在showDialog()方法中。后期还优化了日期选择,为了方便,没有使用日历插件,而是结合弹框和选择框,做了个简易的日期选择,如下图所示。
当只有一种排期表时,复杂度并不高,而当有多种排期表时,许多逻辑就要调整,要么新增,要么适配,并且服务器也要跟着做相应的修改。
由于数据嵌套了多层,因此在遍历数据转换格式时会比较麻烦。当整合缓存和服务器数据中的单元格样式时,以缓存中的为准,例如缓存中未选中,而服务器是选中,那么忽略服务器中的状态。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了