├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── LICENSE ├── README.md ├── component ├── base.js ├── calendar │ ├── calendar.css │ ├── calendar.js │ ├── demo │ │ └── calendar.html │ ├── images │ │ ├── btn-next.png │ │ ├── btn-prev.png │ │ └── info.png │ ├── readme.md │ └── themes │ │ └── grace │ │ └── skins │ │ └── default │ │ └── skin.css └── weekcalendar │ ├── demo │ ├── test.js │ ├── test.json │ └── week.html │ ├── images │ ├── btn-next.png │ └── btn-prev.png │ ├── readme.md │ ├── weekcalendar.css │ ├── weekcalendar.js │ └── weekcalendar.scss └── lib ├── jquery.min.js ├── mock-min.js └── moment.min.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.min.js 2 | **/lib/* 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": false, 6 | "node": false 7 | }, 8 | // 使用默认的配置 9 | "extends": "eslint:recommended", 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "sourceType": "script" 15 | }, 16 | "globals": { 17 | // 是否允许被重写 18 | "jQuery": false, 19 | "$": false, 20 | "mini": false, 21 | "Util": false, 22 | "epoint": false 23 | }, 24 | // [中文文档: http://eslint.cn/docs/rules/ ](http://eslint.cn/docs/rules/) 25 | "rules": { 26 | "no-const-assign": "warn", 27 | "no-this-before-super": "warn", 28 | "no-undef": "warn", 29 | "no-unreachable": "warn", 30 | "no-unused-vars": "warn", 31 | "constructor-super": "warn", 32 | "valid-typeof": "warn", 33 | "no-delete-var": "warn", 34 | // 分号警告 35 | "semi": "warn", 36 | // 单引号警告 37 | "quotes": ["warn", "single"], 38 | // 强制所有控制语句使用一致的括号风格 39 | "curly": "warn", 40 | // 要求 switch 语句中有 default 分支 41 | "default-case": "warn", 42 | 43 | "no-multiple-empty-lines": "warn", 44 | "no-console": "off", 45 | "no-empty": "off" 46 | } 47 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Douglas Chen [github](https://github.com/cdswyda) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # component-frame 2 | 3 | 一套组件机制,支持自动加载依赖资源、事件、继承、重写等 4 | 5 | 目前已经在里面新增了两个日历组件: 6 | 7 | - 一个月视图日历,支持在日历中插入任意内容。 [在线演示](https://cdswyda.github.io/component-frame/component/calendar/demo/calendar.html) [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/calendar) 8 | - 二维周视图日历。 [在线演示](https://cdswyda.github.io/component-frame/component/weekcalendar/demo/week.html) [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/weekcalendar) 9 | 10 | ## 基本功能 11 | 12 | - 提供了一套完整的事件机制 13 | - 提供控件的继承方法、重写方法 14 | - 自动加载控件依赖资源 15 | - 在资源加载完成后自动调用控件的初始化方法 16 | 17 | ## 基本使用 18 | 19 | 1. 引入控件机制的js文件 20 | 1. 引入实际要使用控件的js文件 21 | 1. 调用统一的初始化方法 `epctrl.init(name/* 控件名称 */, cfg /* 控件初始化配置*/)` 即可 22 | 23 | ## 控件机制api 24 | 25 | ### 继承方法 26 | 27 | ```js 28 | /** 29 | * 继承方法 30 | * 31 | * @param {String} subName 新Class的名称 32 | * @param {String} spName 父类的名称 33 | * @param {Object} overrides 要新增或重写到原型上的属性和方法集合 34 | * @returns 继承后的Class 35 | */ 36 | epctrl.extend(subName, spName, overrides); 37 | ``` 38 | 39 | 继承控件时,如果指定的的父类控件还未加载,会自动加载父类资源。 40 | 41 | ### 重写方法 42 | 43 | ```js 44 | /** 45 | * 重写方法 46 | * 47 | * @param {String} destName 要重写的控件的名称 48 | * @param {Object} proto 要重写的属性、方法集合 49 | * @returns 重写后的控件 50 | */ 51 | epctrl.overwrite(destName, proto); 52 | ``` 53 | 54 | 重写控件时,如果指定的的父类控件还未加载,会自动加载父类资源。 55 | 56 | ### 初始化方法 57 | 58 | 基于此机制的控件,统一使用此方法进行初始化。 59 | 60 | ```js 61 | /** 62 | * 初始化方法 63 | * @param {String} name 控件名称 64 | * @param {Object} cfg 控件需要的配置 65 | * @returns 实例控件 66 | */ 67 | epctrl.init(name, cfg); 68 | ``` 69 | 70 | 此方法中会自动实例化出一个指定的控件,并在此控件依赖资源加载完成后自动调用创建方法,返回实例控件。 71 | 72 | ### 控件基类 73 | 74 | `epctrl.Control` 为控件机制中所有控件的基类,其中提供了事件机制,以及自动引入相关资源,并在所有依赖资源加载完成后,自动调用控件的初始化方法。 75 | 76 | 基类构造函数在执行时将读取配置中的events属性,如果存在,则自动将指定的响应事件绑定到实例对象上。同时,如果此控件需要皮肤,则会自动绑定一个仅指向一次的加载皮肤事件,以便在加载资源时自动引入皮肤资源。 77 | 78 | **方法** 79 | 80 | 方法名称 | 参数 | 说明 81 | -- | -- | -- 82 | `on(type, fn, scope)` | `type` :字符串类型,表示事件名称
`fn` :事件处理函数
`scope` : 额外指定的事件处理函数执行时的上下文,不指定时为控件实例对象 | 绑定一个事件 83 | `off(type, fn)` | `type` :字符串类型,表示事件名称
`fn` :事件处理函数 | 取消绑定一个事件,第二个参数可省略,省略时取消绑定当前类型下的所有处理事件 84 | `fire(type, data)` | `type` :字符串类型,表示事件名称
`data` 一个对象,触发此事件时的数据 | 触发一个事件,返回事件对象
事件处理函数的实参中除了fire时传递的data中的所有属性之外,还包括以下通用属性:
`type` : 事件类型
`origin` : 事件所在实例对象,是指为当前控件的实例对象
`scope` : 用户指定的事件处理函数的执行上下文,未指定时为undefined。
`cancel` : 是否取消,用于一些情况下取消事件的后续操作。 85 | `one(type, fn, scope)` | 和 `on` 方法相同 | 绑定一个仅执行一次的事件 86 | `addCss(url)` | `url` :字符串类型,表示css资源路径 | 为此控件添加一个css资源 87 | `addJs(url)` | `url` :字符串类型,表示js资源路径 | 为此控件添加一个js资源 88 | `setCss(url)` | `url` :字符串类型或数组类型,表示css资源路径,数组表示有多个资源按顺序加载 | 为此控件设置css资源
有相应的get方法,将set换为get即可,表示获取。 89 | `setJs(url)` | `url` :字符串类型或数组类型,表示js资源路径,数组表示有多个资源按顺序加载 | 为此控件设置js资源
有相应的get方法,将set换为get即可,表示获取。 90 | `setHtml(url)` | `url` :字符串类型,表示html资源路径 | 为此控件设置html资源
有相应的get方法,将set换为get即可,表示获取。 91 | `setHtmlContainer(selector)` | `selector` :字符串类型,一个jQuery选择器,表示html要插入的容器 | 为此控件设置html资源插入的容器
有相应的get方法,将set换为get即可,表示获取。 92 | `throwError(msg)` | `msg` 一个字符串,表示错误信息 | 抛出一个错误 93 | `print(msg, type)` | `msg` 一个字符串,表示要在控制台输出的信息
`type` 可选值为 `"info"` 、 `"warn"` 、 `"error"` 之一,表示输出信息类型 | 在控制台输出信息 94 | `loadSource()` | | 自动加载控件资源,此方法中将读取控件所依赖的css、html和js资源,并在html和js资源加载完成后自动调用控件的 `create` 方法。
**此方法无需手动调用**,会自动在 `epctrl.init` 方法中调用。 95 | `create()` | | 控件所依赖资源加载完成后的真实创建方法。
**控件应该实现此方法**
不同控件应该有不同的初始化方法,不应该直接调用到此基类的创建方法,此方法被调用时将抛出一个错误。 96 | `_cacheFn(fn [,fnArgs])` | fn 一个函数为迟延执行的方法
其他后续参数将被传递给指定的fn |缓冲一个方法,直到控件初始化完成后再真实调用。 | 97 | 98 | > 以上方法中,最终用户会使用的仅为 `on`、`one` 方法,其余均是提供给开发者在新控件中使用的。 99 | 100 | **提供属性** 101 | 102 | 属性名称 | 说明 103 | -- | -- 104 | `type` | 类型为一个字符串 ,值和控件的构造函数名相同,不区分大小写,用于标识此控件的类型。
每个控件在继承时必须重写此属性。 105 | `useSkin` | 值为一个布尔值,表示此控件是否需要皮肤,默认在基类上提供,值为false,如果某控件需要换肤,需要设置为true,并提供相应的皮肤资源。 106 | `cssUrl` | 值为一个数组,表示此控件需要加载的所有的css资源路径 107 | `jsUrl` | 值为一个数组,表示此控件需要加载的所有的js资源路径 108 | `htmlUrl` | 值为一个字符串,表示此控件需要加载的html资源路径 109 | `htmlContainer` | 控件html资源加载成功后要插入的容器,值为一个jq选择器或jq对象或HTMLElement 110 | 111 | > 控件基类提供的以上属性中,均为控件开发者使用的,最终用户无需关注。 112 | 113 | **事件** 114 | 115 | 触发事件时,除了触发时传递的其他属性之外,固定有以下属性: 116 | 117 | ```js 118 | { 119 | type: String, // 事件类型 120 | origin: Object, // 事件绑定到的对象,即当前控件 121 | cancel: Boolean, // 是否取消 122 | scope: Object // 事件处理函数执行的上下文对象,绑定事件时未指定则同origin 123 | } 124 | ``` 125 | 126 | 此外控件基类中自动处理流程中会按顺序触发以下事件: 127 | 128 | 事件名称 | 额外参数 | 说明 129 | -- | -- | -- 130 | `beforeSourceLoad` | `cssUrl` : 将要加载的css资源
`jsUrl` :将要加载的js资源 | 控件开始加载所需要的资源之前触发。 131 | `beforeHTMLLoad` | `htmlUrl` : 将要加载的HTML资源 | 控件HTML资源加载前触发 132 | `beforeHtmlInsert` | `htmlUrl` : 将要加载的HTML资源
`content` :获取到的HTML内容
`htmlContainer` : HTML将要插入的容器 | 控件HTML资源加载成功,插入页面前触发。 133 | `afterHTMLLoad` | 同 beforeHtmlInsert | 控件HTML资源加载成功,并插入页面后触发。 134 | `beforeJsload` | `jsUrl` :将要加载的js资源 | js资源开始加载前触发。 135 | `afterJsLoad` | `jsUrl` :加载的js资源 | js资源加载完成后触发。 136 | `afterSourceLoad` | `cssUrl` : 加载的css资源
`jsUrl` :加载的js资源 | 控件加载资源完成后触发。 137 | 138 | ## 控件机制约定和规范 139 | 140 | ### 命名约定 141 | 142 | - 控件的构造函数名称遵循帕斯卡命名规范 143 | - 控件继承时,必须为此控件指定 `type` 属性,类型为字符,值和构造函数名称相同 144 | - 控件存放的js、css文件名称和控件的构造函数名称一致 145 | - 控件存放的js、css文件名称全部小写 146 | 147 | ### 控件资源组织 148 | 149 | 一个控件形成一个文件夹,以自己的控件名称(即控件的构造函数名称)命名,全部小写,放在控件基类文件(`base.js`)所在的目录下。 150 | 151 | 控件的资源目录组织结构应如下所示: 152 | 153 | ``` 154 | component 155 | |- controlname 156 | |- controlname.js // 控件js 157 | |- controlname.css // 控件css 158 | |- readme // 关于此控件的说明 159 | |- images // 控件需要的图片资源 160 | |- 图片资源1 161 | |- 图片资源2 162 | |- ... 163 | |- 图片资源n 164 | |- themes // 皮肤资源 如果控件需要换肤则必须提供 165 | |- grace // 主题名称 如dream、classic、imac等 166 | |- skins 167 | |- default // 皮肤的名称 168 | |- skin.css // 此皮肤的css文件 169 | |- chinared 170 | |- skin.css 171 | |- dream 172 | |- .. 173 | |- classic 174 | |- .. 175 | 176 | ``` 177 | 178 | 以下为一个控件的结构示例 179 | 180 | ``` 181 | component 182 | |- calendar 183 | |- calendar.js // 控件js 184 | |- calendar.css // 控件css 185 | |- readme.md // 关于此控件的说明 186 | |- images // 控件需要的图片资源 187 | |- btn-prev.png 188 | |- btn-next.png 189 | |- themes // 皮肤资源 如果控件需要换肤则必须提供 190 | |- grace // 主题名称 如dream、classic、imac等 191 | |- skins 192 | |- default // 皮肤的名称 193 | |- skin.css // 此皮肤的css文件 194 | |- chinared 195 | |- skin.css 196 | ``` 197 | 198 | ### 配置 199 | 200 | **开发时的约定** 201 | 202 | 除以上命名和资源组织之外,控件开发时还需要注意: 203 | 204 | - 每个控件需要实现自己的 `create` 方法,并在控件真实创建完成后触发 `afterCreate` 事件。 205 | - `useSkin` 属性:表示此控件是否需要切换皮肤 206 | - 通常一种控件是否需要换肤在开发时就可以确定,因此开发者应该在开发时为此控件指定好此属性,需要皮肤的情况下,在资源加载前,会自动获取当前皮肤并加载。 207 | - 如果需要切换皮肤,必须提供相应的皮肤资源,皮肤资源组织结构参考上面的控件资源组织结构。 208 | - 关于 `setCss()`、`setJs` 方法 和 `addCss()` 、 `addJs()` 方法的作用和区别。 209 | - 都是为控件指定需要的css和js资源。 210 | - 直接使用 `this.setCss()` 和 `this.setCss()` 时,参数为一个字符串或数组,值为资源路径,表示直接为此控件指定所需要的css和js资源。 211 | - 使用 `this.addCss()` 和 `this.addJs()` 方法,传递一个资源的路径时,此时是在父类css或js资源的基础上新加入指定的css或js文件。 212 | 213 | **关于用户使用时配置的约定** 214 | 215 | - 配置 `cfg` 必须为一个对象。 216 | - 配置对象中的 `events` 属性为一个对象,键名为事件名称,值为处理函数。用于绑定控件在初始化过程中就需要的事件。没有则无需指定。 217 | 218 | ## 继承基类创建一个新的控件 219 | 220 | 继承固定格式如下: 221 | 222 | ```js 223 | // 新控件的构造函数 224 | epctrl.Calendar = function (cfg) { 225 | epctrl.Calendar.superclass.constructor.apply(this, arguments); 226 | 227 | // TODO 控件上的其他操作 228 | }; 229 | // 继承关系 230 | epctrl.extend('Calendar', 'Control', { 231 | // 此控件类型 232 | type:"Calendar", 233 | // 创建方法,在资源加载完成后自动调用 234 | create: function () { 235 | // TODO 此控件的创建方法 236 | } 237 | }); 238 | ``` 239 | 240 | 以上展示了继承基类创建一个控件名称为 **Calendar** 的新控件的固定写法和必要部分。 241 | 242 | 可分为两个部分:构造函数和继承关系 243 | 244 | **构造函数** 245 | 246 | 1. 首先在控件机制的命名空间下新增一个名为 `Calendar` 的构造函数,参数为此控件的配置。 247 | 1. 在构造函数内部首先调用此控件的 `superclass.constructor` 方法,作用为先执行一遍父类的构造函数,固定写法为: `epctrl.新增的控件.superclass.constructor.apply(this, arguments)` , 此处的Calendar控件,即:`epctrl.Calendar.superclass.constructor.apply(this, arguments);` 248 | 249 | **继承关系** 250 | 251 | 1. 直接调用 `epctrl.extend` 方法,第一个参数为当前控件的名称,第二个参数为父类控件的名称,第三个参数为要新增或覆盖的属性、方法集合。 252 | 1. 在第三个参数中,必须有 `type` 属性和 `create()` 方法,`type` 属性用于唯一标识当前控件的名称,值为一个字符串,约定值和当前控件构造函数名称相同;`create()` 方法为此控件的真实创建方法。 253 | 254 | ## 继承已有控件创建一个新的控件 255 | 256 | 使用和基于基类创建控件类似,首先也是创建自己的构造函数,并在构造函数内首先调用父类的构造函数,之后指定此控件的继承关系和需要新增或重写的方法即可。 257 | 258 | 例如继承上面创建的 `Calendar` 控件,来创建一个新的 `ExtraCalendar` : 259 | 260 | ```js 261 | // 新控件构造函数 262 | epctrl.ExtraCalendar = function (cfg) { 263 | epctrl.ExtraCalendar.superclass.constructor.apply(this, arguments); 264 | 265 | // 到此 所有在Calendar构造函数中处理的内容已经执行,之后在加入或修改即可。 266 | // TODO 267 | }; 268 | 269 | // 继承关系 270 | epctrl.extend('ExtraCalendar', 'Calendar', { 271 | // 此控件类型 必须重写 272 | type:"ExtraCalendar", 273 | // 创建方法,在资源加载完成后自动调用 274 | // 如果此控件的创建方法可以复用Calendar中的,则可以不用重写 275 | create: function () { 276 | // TODO 此控件的创建方法 277 | } 278 | }); 279 | ``` 280 | 281 | ## 重写一个已有控件 282 | 283 | 如果已有控件的某些方法不符合目前的需求,则可以重写这个已有控件的部分属性和方法。 284 | 285 | 使用 `epctrl.overwrite(destName, proto)` 方法即可。 286 | 287 | 如重写之前创建的 `ExtraCalendar` 控件: 288 | 289 | ```js 290 | // 重写控件 291 | epctrl.overwrite('ExtraCalendar', { 292 | // 需要新增或者重写的属性方法 293 | // ... 294 | }); 295 | ``` 296 | -------------------------------------------------------------------------------- /component/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一套组件机制 3 | * author: Douglas Chen 4 | */ 5 | 6 | /* global epctrl*/ 7 | 8 | (function (win, $) { 9 | 10 | if (!$ || typeof $ != 'function') { 11 | throw new Error('epctrl require jQuery'); 12 | } 13 | 14 | var isIE67 = '\v' == 'v', 15 | isIE8 = !!document.all && document.querySelector && !document.addEventListener; 16 | 17 | var getPathByName = function (name) { 18 | name = name.toLowerCase(); 19 | 20 | var url = epctrl._rootPath + '/' + name + '/' + name + '.js'; 21 | 22 | return url; 23 | }; 24 | 25 | var throwError = function (msg) { 26 | throw new Error(msg); 27 | }; 28 | 29 | /** 30 | * 同步加载资源,用于继承和重写时自动引入父类的定义 31 | * 32 | * @param {String} url 父类名称 33 | * @returns jQuery.promise 34 | */ 35 | var loadSupperSync = function (name) { 36 | return $.ajax({ 37 | url: getPathByName(name), 38 | type: 'get', 39 | dataType: 'TEXT', 40 | async: false 41 | }).done(function (data) { 42 | var script = document.createElement('script'); 43 | script.text = data; 44 | document.getElementsByTagName('head')[0].appendChild(script); 45 | }).fail(throwError); 46 | }; 47 | 48 | /** 49 | * 加载父类资源 50 | * 51 | * @param {String} supperCls 父类控件名称 52 | * @returns undefined 53 | */ 54 | var loadSupperCls = function (supperCls) { 55 | // 已经存在则不用操作 56 | if (typeof epctrl[supperCls] == 'function') { 57 | return; 58 | } 59 | 60 | // 否则同步加载父类资源 61 | loadSupperSync(supperCls); 62 | }; 63 | 64 | /** 65 | * 继承实现 66 | * 67 | * @param {Function} newClass 新Class的构造函数 68 | * @param {Function} sp 父类的构造函数 69 | * @param {Object} overrides 要新增或重写到原型上的属性和方法集合 70 | * @returns 继承后的Class 71 | */ 72 | var extend = function (newClass, sp, overrides) { 73 | overrides = overrides || {}; 74 | 75 | if (typeof sp != 'function') { 76 | return this; 77 | } 78 | 79 | // 自动给新控件中加入用于记录资源是否加载的记录对象 80 | if (!overrides._jsPromise) { 81 | // js资源的promise 82 | overrides._jsPromise = {}; 83 | } 84 | 85 | var sb = newClass, 86 | sbp = sb.prototype, 87 | spp = sp.prototype; 88 | if (sb.superclass == spp) { 89 | return; 90 | } 91 | sb.superclass = spp; 92 | sb.superclass.constructor = sp; 93 | 94 | var p; 95 | 96 | for (p in spp) { 97 | sbp[p] = spp[p]; 98 | } 99 | if (overrides) { 100 | for (p in overrides) { 101 | sbp[p] = overrides[p]; 102 | } 103 | } 104 | return sb; 105 | }; 106 | 107 | /** 108 | * 重写实现 109 | * 110 | * @param {Object} dest 要重写的控件 111 | * @param {Object} proto 要重写的属性、方法集合 112 | * @returns 重写后的控件 113 | */ 114 | var overwrite = function (dest, proto) { 115 | var d; 116 | if (typeof dest == 'function') { 117 | d = dest.prototype; 118 | } else if (typeof dest == 'object') { 119 | d = dest; 120 | } else { 121 | return this; 122 | } 123 | 124 | for (var c in proto) { 125 | d[c] = proto[c]; 126 | } 127 | return dest; 128 | }; 129 | 130 | var Control = function () { 131 | // 添加事件存储对象 132 | this._events = {}; 133 | // 添加css和js资源属性 134 | this.cssUrl = []; 135 | this.jsUrl = []; 136 | // html 加载 137 | this.htmlUrl = ''; 138 | this.htmlContainer = false; 139 | 140 | // 缓存需要迟延执行的方法 141 | this._FnQueues = []; 142 | 143 | // 处理初始化的事件绑定 144 | // 统一约定初始化就需要的事件放在参数的events中,键名为事件名称,值为处理函数 145 | var cfg = arguments[0], 146 | events = cfg.events; 147 | // 加入事件 148 | if (events) { 149 | for (var p in events) { 150 | if (events.hasOwnProperty(p)) { 151 | this.on(p, events[p]); 152 | } 153 | } 154 | } 155 | 156 | // 绑定一个创建完成事件,处理迟延执行方法队列 157 | this.on('afterCreate', this._onCreate); 158 | }; 159 | 160 | Control.prototype = { 161 | // 控件唯一标识 162 | type: 'control', 163 | constructor: Control, 164 | // js资源的promise 在继承方法中自动重写此属性保证每个控件资源加载独立 165 | _jsPromise: {}, 166 | // 控件所在路径 167 | _getControlPath: function () { 168 | return 'frame/fui/js/widgets/epcontrols/' + this.type.toLowerCase() + '/'; 169 | }, 170 | // 事件实现 171 | /** 172 | * 绑定事件 173 | * 174 | * @param {String} type 事件类型 175 | * @param {Function} fn 事件处理函数 176 | * @param {Object} scope 要为事件处理函数绑定的执行上下文 177 | * @returns 当前实例对象 178 | */ 179 | on: function (type, fn, scope) { 180 | if (typeof type != 'string') { 181 | // console && console.error && console.error('the first arguments type is requird as string'); 182 | this.print('the first arguments type is requird as string', 'error'); 183 | return this; 184 | } 185 | if (typeof fn != 'function') { 186 | // console && console.error && console.error('the second arguments fn is requird as function'); 187 | this.print('the second arguments fn is requird as function', 'error'); 188 | return this; 189 | } 190 | 191 | type = type.toLowerCase(); 192 | 193 | if (!this._events[type]) { 194 | this._events[type] = []; 195 | } 196 | 197 | this._events[type].push(scope ? [fn, scope] : [fn]); 198 | 199 | return this; 200 | }, 201 | 202 | /** 203 | * 取消绑定一个事件 204 | * 205 | * @param {String} type 取消绑定的事件名称 206 | * @param {Function} fn 要取消绑定的事件处理函数,不指定则移除当前事件类型下的全部处理函数 207 | * @returns 当前实例对象 208 | */ 209 | off: function (type, fn) { 210 | type = type.toLowerCase(); 211 | 212 | var eventArr = this._events[type]; 213 | 214 | if (!eventArr || !eventArr.length) { 215 | return this; 216 | } 217 | 218 | if (!fn) { 219 | this._events[type] = eventArr = []; 220 | } else { 221 | for (var i = 0; i < eventArr.length; ++i) { 222 | if (fn === eventArr[i][0]) { 223 | eventArr.splice(i, 1); 224 | // 1、找到后不能立即 break 肯存在一个事件一个函数绑定多次的情况 225 | // 删除后数组改变,下一个仍然需要遍历处理! 226 | --i; 227 | } 228 | } 229 | } 230 | return this; 231 | }, 232 | /** 233 | * 触发事件 234 | * 235 | * @param {String} type 触发事件的名称 236 | * @param {Anything} data 要额外传递的数据,事件处理函数参数如下 237 | * event = { 238 | // 事件类型 239 | type: type, 240 | // 绑定的源,始终为当前实例对象 241 | origin: this, 242 | // 事件处理函数中的执行上下文 为 this 或用户指定的上下文对象 243 | scope :this/scope 244 | // 其他数据 为fire时传递的数据 245 | data: data 246 | } 247 | * @returns 事件对象 248 | */ 249 | fire: function (type, data) { 250 | type = type.toLowerCase(); 251 | 252 | var eventArr = this._events[type]; 253 | 254 | var fn, 255 | // event = { 256 | // // 事件类型 257 | // type: type, 258 | // // 绑定的源 259 | // origin: this, 260 | // // scope 为 this 或用户指定的上下文, 261 | // // 其他数据 262 | // data: data, 263 | // // 是否取消 264 | // cancel: false 265 | // }; 266 | // 上面对自定义参数的处理不便于使用 将相关属性直接合并到事件参数中 267 | event = $.extend({ 268 | // 事件类型 269 | type: type, 270 | // 绑定的源 271 | origin: this, 272 | // scope 为 this 或用户指定的上下文, 273 | // 其他数据 274 | // data: data, 275 | // 是否取消 276 | cancel: false 277 | }, data); 278 | 279 | if (!eventArr) { 280 | return event; 281 | } 282 | 283 | for (var i = 0, l = eventArr.length; i < l; ++i) { 284 | fn = eventArr[i][0]; 285 | event.scope = eventArr[i][1] || this; 286 | fn.call(event.scope, event); 287 | } 288 | return event; 289 | }, 290 | /** 291 | * 绑定一个只执行一次的事件 292 | * 293 | * @param {String} type 事件类型 294 | * @param {Function} fn 事件处理函数 295 | * @param {Object} scope 要为事件处理函数绑定的执行上下文 296 | * @returns 当前实例对象 297 | */ 298 | one: function (type, fn, scope) { 299 | var that = this; 300 | 301 | function nfn() { 302 | // 执行时 先取消绑定 303 | that.off(type, nfn); 304 | // 再执行函数 305 | fn.apply(scope || that, arguments); 306 | } 307 | 308 | this.on(type, nfn, scope); 309 | 310 | return this; 311 | }, 312 | // 资源加载实现 313 | // 动态加载js 314 | _loadJsPromise: function (url) { 315 | 316 | // 无 url 则直接成功 317 | if (!url) { 318 | return $.Deferred().resolve().promise(); 319 | } 320 | 321 | // 已经加载 则返回之前的promise 322 | if (this._jsPromise[url]) { 323 | return this._jsPromise[url]; 324 | } 325 | 326 | var that = this, 327 | dtd = $.Deferred(), 328 | script = document.createElement('script'); 329 | 330 | script.type = 'text/javascript'; 331 | 332 | // IE8- IE9+ 已经支持onload等,控制更加精确 333 | if ((isIE67 || isIE8) && script.readyState) { 334 | script.onreadystatechange = function () { 335 | if (script.readyState == 'loaded' || script.readyState == 'complete') { 336 | dtd.resolve(); 337 | script.onreadystatechange = null; 338 | } 339 | }; 340 | } else { 341 | script.onload = function () { 342 | dtd.resolve(); 343 | script.onload = null; 344 | }; 345 | script.onerror = function () { 346 | dtd.reject(); 347 | script.onerror = null; 348 | that._jsPromise[url] = null; 349 | throwError(url + ':此资源加载失败!'); 350 | }; 351 | } 352 | 353 | this.fire('beforeOneJsload', { 354 | jsUrl: url 355 | }); 356 | 357 | script.src = epctrl._rootPath + '/' + url; 358 | // append to head 359 | document.getElementsByTagName('head')[0].appendChild(script); 360 | 361 | return (this._jsPromise[url] = dtd.promise()); 362 | }, 363 | loadJs: function (urls, callback) { 364 | 365 | var successDtd = $.Deferred(); 366 | 367 | if (!urls || !urls.length) { 368 | // jq 3.0以下版本的此处有问题 此处会立即执行 是一个bug 不能直接使用 369 | // return $.Deferred().resolve().promise().then(function () { 370 | // callback && callback(); 371 | // }); 372 | 373 | if ($.fn.jquery >= '3') { 374 | return $.Deferred().resolve().promise().then(function () { 375 | callback && callback(); 376 | }); 377 | } else { 378 | 379 | // 用迟延模拟异步 380 | setTimeout(function () { 381 | successDtd.resolve(); 382 | }); 383 | return successDtd.promise().then(function () { 384 | callback && callback(); 385 | }); 386 | } 387 | } 388 | 389 | var that = this; 390 | if (typeof urls == 'string') { 391 | urls = [urls]; 392 | } 393 | 394 | this.fire('beforeJsload', { 395 | jsUrl: urls 396 | }); 397 | 398 | var loads = JSON.parse(JSON.stringify(urls)); 399 | 400 | function loadNext() { 401 | // 单个资源加载完成 402 | that.fire('afterOneJsload', { 403 | js: loads[0] 404 | }); 405 | 406 | // 删除第一个元素 407 | loads.splice(0, 1); 408 | 409 | if (loads.length) { 410 | // 还有时继续加载 411 | return that._loadJsPromise(loads[0]) 412 | .done(loadNext) 413 | .fail(that.throwError); 414 | } else { 415 | // 待加载的js中没有元素时代表全部资源加载完毕 416 | that.fire('afterJsLoad', { 417 | jsUrl: urls 418 | }); 419 | // callback && callback(); 420 | return $.Deferred().resolve().promise().then(function () { 421 | callback && callback(); 422 | }); 423 | } 424 | } 425 | 426 | return this._loadJsPromise(loads[0]) 427 | .then(loadNext); 428 | }, 429 | /** 430 | * 加载css 431 | * 432 | * @param {String/Array} urls 要加载的css资源的路径,多个用数组表示。可为根路径写起或相对路径 433 | * @param {HTMLElement} target 要插入到的目标位置,默认为head元素 434 | * @param {String} pos 要插入的位置,值为After或Before 435 | */ 436 | loadCss: function (urls, target, pos) { 437 | // 外部组件需要引用的css可能有多个 438 | if (typeof urls == 'string') { 439 | urls = [urls]; 440 | } 441 | 442 | var link = null, 443 | head = document.getElementsByTagName('head')[0]; 444 | for (var i = 0, l = urls.length; i < l; ++i) { 445 | 446 | if (document.getElementById('style-' + this.type + (l > 1 ? '-' + i : ''))) { 447 | // 当前需要的css已经存在是则跳过加载 避免重复加载 448 | continue; 449 | } 450 | 451 | link = document.createElement('link'); 452 | link.rel = 'stylesheet'; 453 | // 如果仅一个 则id为 style-当前控件类型 454 | // 多个css时 id 为 style-当前控件类型-i 455 | link.id = 'style-' + this.type + (l > 1 ? '-' + i : ''); 456 | 457 | link.href = epctrl._rootPath + '/' + urls[i]; 458 | 459 | if (!target) { 460 | head.appendChild(link); 461 | } else { 462 | if (pos == 'Before') { 463 | head.insertBefore(link, target); 464 | } else { 465 | head.appendChild(link); 466 | } 467 | } 468 | } 469 | }, 470 | /** 471 | * 加载一段html 472 | * 473 | * @param {String} url 加载地址 474 | * @returns 当前实例对象 475 | */ 476 | loadHtml: function (url) { 477 | // 无url则表示不用加载 直接返回成功的 478 | if (!url || !this.htmlContainer) { 479 | return $.Deferred().resolve().promise(); 480 | } 481 | 482 | var ev = this.fire('beforeHTMLLoad', { 483 | htmlUrl: url 484 | }); 485 | 486 | this.htmlUrl = url = ev.htmlUrl; 487 | 488 | var that = this; 489 | 490 | return $.ajax({ 491 | url: epctrl._rootPath + '/' + url, 492 | type: 'GET', 493 | async: true, 494 | dataType: 'HTML' 495 | }).done(function (data) { 496 | var ev = that.fire('beforeHtmlInsert', { 497 | htmlUrl: url, 498 | content: data, 499 | htmlContainer: that.htmlContainer 500 | }); 501 | 502 | // 处理用户修改 503 | data = ev.content; 504 | that.htmlContainer = ev.htmlContainer; 505 | 506 | // 插入到页面 507 | data && that.htmlContainer && $(data).appendTo(that.htmlContainer); 508 | that.fire('afterHTMLLoad', { 509 | htmlUrl: url, 510 | content: data, 511 | htmlContainer: that.htmlContainer 512 | }); 513 | }); 514 | }, 515 | // 用于添加额外的css资源 516 | // 如控件中只加载了基础的资源,有些插件的某些功能还需要其他额外内容来支持 517 | // 直接作为统一的加载好久比较浪费了 518 | // 故提供此方法,用于在控件实例化之前将其加入即可 519 | addCss: function (cssUrl) { 520 | 521 | if (!cssUrl) { 522 | return; 523 | } 524 | 525 | var cssSources = this.cssUrl; 526 | 527 | // 如果是字符串则转化为数组 528 | if (cssUrl + '' === cssUrl) { 529 | cssUrl = [cssUrl]; 530 | } 531 | 532 | $.each(cssUrl, function (i, item) { 533 | cssSources.push(item); 534 | }); 535 | }, 536 | // 为控件设置css资源 537 | setCss: function (url) { 538 | if (url + '' === url) { 539 | return this.cssUrl.push(url); 540 | } else { 541 | return (this.cssUrl = url); 542 | } 543 | }, 544 | // 为控件设置js资源 545 | setJs: function (url) { 546 | if (url + '' === url) { 547 | return this.jsUrl.push(url); 548 | } else { 549 | return (this.jsUrl = url); 550 | } 551 | }, 552 | // 设置控件需要的html资源 553 | setHtml: function (url) { 554 | return (this.htmlUrl = url); 555 | }, 556 | // 设置HTML插入的容器 557 | setHtmlContainer: function (selector) { 558 | return (this.htmlContainer = selector); 559 | }, 560 | getHtmlContainer: function () { 561 | return this.htmlContainer; 562 | }, 563 | getHtml: function () { 564 | return this.htmlUrl; 565 | }, 566 | getCss: function () { 567 | return this.cssUrl; 568 | }, 569 | getJs: function () { 570 | return this.jsUrl; 571 | }, 572 | // 用于添加额外的css资源 573 | addJs: function (jsUrl) { 574 | 575 | if (!jsUrl) { 576 | return; 577 | } 578 | 579 | var jsSources = this.jsUrl; 580 | 581 | if (jsUrl + '' === jsUrl) { 582 | jsUrl = [jsUrl]; 583 | } 584 | 585 | $.each(jsUrl, function (i, item) { 586 | jsSources.push(item); 587 | }); 588 | }, 589 | /** 590 | * 自动加载此控件的所有资源,并在资源加载完成后自动调用控件的create方法 591 | */ 592 | loadSource: function () { 593 | var that = this; 594 | 595 | // 触发资源加载前事件 596 | var sourseEv = this.fire('beforeSourceLoad', { 597 | cssUrl: this.cssUrl, 598 | jsUrl: this.jsUrl 599 | }); 600 | 601 | // 同步事件中的修改 602 | this.cssUrl = sourseEv.cssUrl; 603 | this.jsUrl = sourseEv.jsUrl; 604 | 605 | // css 资源 异步加载 不影响 606 | if (this.cssUrl) { 607 | this.loadCss(this.cssUrl); 608 | } 609 | 610 | // 加载html和js资源 611 | this.loadHtml(this.htmlUrl) 612 | .then(function () { 613 | return that.loadJs(that.jsUrl, function () { 614 | that.fire('afterSourceLoad', { 615 | cssUrl: that.cssUrl, 616 | jsUrl: that.jsUrl 617 | }); 618 | // 此处仅能保证css和js加载完毕,不能确定控件是否创建完毕,因为有的控件的初始化和创建仍然是异步的 619 | that.create(); 620 | }); 621 | }); 622 | }, 623 | 624 | // 不同控件的初始化方法各不相同,需要每个控件自行实现这个方法,进入此方法时 相关资源已经加载完毕 625 | create: function () { 626 | // 触发创建前事件 627 | // this.fire('beforeCreate'); 628 | 629 | this.throwError('控件未提供初始化方法,初始化失败!'); 630 | 631 | // 在这里实现控件的创建或初始化机制 完成后触发完成创建的事件 632 | // this.fire('afterCreate'); 633 | }, 634 | // 在创建完成后检查缓存队列 635 | _onCreate: function (e) { 636 | // 真实的触发源 637 | var origin = e.origin, 638 | cache = origin._FnQueues, 639 | item; 640 | 641 | while (cache.length) { 642 | item = cache.splice(0, 1)[0]; 643 | 644 | item[0].apply(origin, item[1]); 645 | } 646 | }, 647 | /** 648 | * 缓存要在创建完成后再执行的方法 649 | * 650 | * @param {Function} fn 要迟延执行的方法 651 | * @param {any} 此方法执行时的其他参数 652 | * @returns 653 | */ 654 | _cacheFn: function (fn) { 655 | if (typeof fn != 'function') { 656 | this.throwError('The first argument must be a function'); 657 | return; 658 | } 659 | 660 | var args = Array.prototype.slice.call(arguments, 1); 661 | 662 | this._FnQueues.push([fn, args]); 663 | }, 664 | 665 | // 抛出一个错误,参数为错误文本或错误对象 666 | throwError: throwError, 667 | // 控制台打印信息 668 | print: function (msg, type) { 669 | // type = type || 'info'; 670 | if (!win.console || !win.console.log) { 671 | return; 672 | } 673 | 674 | switch (type) { 675 | case 'info': 676 | console.log(msg); 677 | break; 678 | case 'warn': 679 | console.warn(msg); 680 | break; 681 | case 'error': 682 | console.error(msg); 683 | break; 684 | default: 685 | console.log(msg); 686 | break; 687 | } 688 | } 689 | 690 | }; 691 | 692 | win.epctrl = { 693 | // 组件存放的目录 694 | _rootPath: '/component', 695 | /** 696 | * 继承实现 697 | * 698 | * @param {Function} newClass 新Class的构造函数 699 | * @param {String} spName 父类的名称 700 | * @param {Object} overrides 要新增或重写到原型上的属性和方法集合 701 | * @returns 继承后的Class 702 | */ 703 | extend: function (sbName, spName, overrides) { 704 | // 首字符转化为大写 705 | var name = sbName.substr(0, 1).toUpperCase() + sbName.substr(1), 706 | newClass = this[name]; 707 | 708 | if (typeof newClass != 'function') { 709 | throw new Error('在epctrl下未找到' + sbName + '的定义,请确认控件名称正确!'); 710 | } 711 | 712 | loadSupperCls(spName); 713 | return extend(newClass, epctrl[spName], overrides); 714 | }, 715 | // 重写实现 716 | overwrite: function (destName, proto) { 717 | loadSupperCls(destName); 718 | return overwrite(epctrl[destName], proto); 719 | }, 720 | // control 基类 721 | Control: Control, 722 | // 统一的初始化方法 723 | init: function (name, cfg) { 724 | var controlName = name.substr(0, 1).toUpperCase() + name.substr(1); 725 | 726 | if (!this[controlName] || typeof this[controlName] != 'function') { 727 | throw new Error('指定的控件:' + name + ' 未找到,请确认控件名称正确!'); 728 | } 729 | 730 | var control = new this[controlName](cfg); 731 | 732 | control.loadSource(); 733 | 734 | return control; 735 | } 736 | }; 737 | 738 | })(this, jQuery); 739 | -------------------------------------------------------------------------------- /component/calendar/calendar.css: -------------------------------------------------------------------------------- 1 | .ep-calendar { 2 | position: relative; 3 | 4 | -webkit-box-sizing: border-box; 5 | -moz-box-sizing: border-box; 6 | box-sizing: border-box; 7 | width: 100%; 8 | height: 100%; 9 | min-height: 260px; 10 | 11 | border: 1px solid #d2d7dd; 12 | } 13 | 14 | 15 | /* 头部 */ 16 | 17 | .ep-calendar-header { 18 | position: relative; 19 | 20 | -webkit-box-sizing: border-box; 21 | -moz-box-sizing: border-box; 22 | box-sizing: border-box; 23 | width: 100%; 24 | height: 40px; 25 | 26 | text-align: center; 27 | 28 | border-bottom: 1px solid #d2d7dd; 29 | } 30 | 31 | .ep-calendar-header:after, 32 | .ep-calendar-header-center:after { 33 | display: table; 34 | clear: both; 35 | 36 | content: ""; 37 | } 38 | 39 | .ep-calendar-header-left { 40 | float: left; 41 | } 42 | 43 | .ep-calendar-header-right { 44 | float: right; 45 | } 46 | 47 | .ep-calendar-header-center { 48 | line-height: 30px; 49 | 50 | display: inline-block; 51 | 52 | margin-top: 9px; 53 | } 54 | 55 | .ep-calendar-header-btn, 56 | .ep-calendar-title { 57 | float: left; 58 | 59 | cursor: pointer; 60 | } 61 | 62 | .ep-calendar-btn-prev, 63 | .ep-calendar-btn-next { 64 | width: 30px; 65 | height: 30px; 66 | 67 | cursor: pointer; 68 | 69 | background-repeat: no-repeat; 70 | background-position: center center; 71 | } 72 | 73 | .ep-calendar-btn-prev { 74 | background-image: url("./images/btn-prev.png"); 75 | } 76 | 77 | .ep-calendar-btn-next { 78 | background-image: url("./images/btn-next.png"); 79 | } 80 | 81 | 82 | /* 主体部分 */ 83 | 84 | .ep-calendar-body { 85 | width: 100%; 86 | 87 | table-layout: fixed; 88 | } 89 | 90 | 91 | /* 星期 */ 92 | 93 | .ep-calendar-daysheader { 94 | line-height: 30px; 95 | 96 | height: 30px; 97 | } 98 | 99 | .ep-calendar-day, 100 | .ep-calendar-date { 101 | width: 14.285%; 102 | 103 | text-align: center; 104 | } 105 | 106 | 107 | /* 日期 */ 108 | 109 | .ep-calendar-dates { 110 | /* */ 111 | } 112 | 113 | .ep-calendar-date-inner { 114 | position: relative; 115 | 116 | overflow: hidden; 117 | 118 | width: 100%; 119 | height: 100%; 120 | } 121 | .ep-calendar-week { 122 | line-height: 30px; 123 | 124 | height: 30px; 125 | } 126 | 127 | .ep-calendar-date { 128 | overflow: hidden; 129 | 130 | padding: 0 3px; 131 | 132 | text-align: center; 133 | vertical-align: top; 134 | 135 | color: #333; 136 | } 137 | 138 | .ep-calendar-date .ep-calendar-date-text { 139 | line-height: 20px; 140 | 141 | display: inline-block; 142 | 143 | width: 20px; 144 | height: 20px; 145 | 146 | border: 1px solid transparent; 147 | } 148 | 149 | .ep-calenday-weekend .ep-calendar-date-text { 150 | color: #e02d2d; 151 | } 152 | 153 | .ep-calendar-othermonth .ep-calendar-date-text { 154 | color: #999; 155 | } 156 | 157 | .ep-calendar-today .ep-calendar-date-text { 158 | color: #fff; 159 | border-color: #56b1ff; 160 | background: #56b1ff; 161 | } 162 | 163 | .ep-calendar-selected .ep-calendar-date-text { 164 | border-color: #56b1ff; 165 | -webkit-border-radius: 2px; 166 | -moz-border-radius: 2px; 167 | border-radius: 2px; 168 | } 169 | 170 | .ep-calendar-menu { 171 | position: absolute; 172 | top: 40px; 173 | left: 50%; 174 | 175 | display: none; 176 | 177 | width: 220px; 178 | height: 226px; 179 | margin-left: -110px; 180 | 181 | text-align: center; 182 | 183 | border: 1px solid #d5dae1; 184 | background: #fff; 185 | -webkit-box-shadow: 0 2px 3px rgba(0,0,0,.15); 186 | -moz-box-shadow: 0 2px 3px rgba(0,0,0,.15); 187 | box-shadow: 0 2px 3px rgba(0,0,0,.15); 188 | } 189 | .ep-calendar-menu.show { 190 | display: block; 191 | } 192 | .ep-calendar-menu-months { 193 | line-height: 24px; 194 | 195 | display: block; 196 | 197 | margin: 10px 14px; 198 | } 199 | .ep-calendar-menu-months:after, 200 | .ep-calendar-menu-years-inner:after { 201 | display: table; 202 | clear: both; 203 | 204 | content: ""; 205 | } 206 | .ep-calendar-menu-month { 207 | font-family: Tahoma,Verdana,宋体; 208 | font-size: 9pt; 209 | 210 | float: left; 211 | 212 | width: 26px; 213 | height: 24px; 214 | margin: 0 10px 4px; 215 | 216 | cursor: pointer; 217 | 218 | border: 1px solid transparent; 219 | -webkit-border-radius: 2px; 220 | -moz-border-radius: 2px; 221 | border-radius: 2px; 222 | } 223 | .ep-calendar-menu-month.selected, 224 | .ep-calendar-menu-month:hover { 225 | color: #f96d41; 226 | border-color: #f9a68c; 227 | background: #fff7f4; 228 | } 229 | .ep-calendar-menu-years { 230 | position: relative; 231 | 232 | height: 56px; 233 | margin: 0; 234 | padding: 10px 15px; 235 | 236 | background: #f6f6f6; 237 | } 238 | .ep-calendar-prev-year, 239 | .ep-calendar-next-year { 240 | position: absolute; 241 | top: 50%; 242 | 243 | display: block; 244 | 245 | width: 15px; 246 | height: 15px; 247 | margin-top: -8px; 248 | 249 | cursor: pointer; 250 | 251 | background-repeat: no-repeat; 252 | background-position: center center; 253 | } 254 | .ep-calendar-prev-year { 255 | left: 0; 256 | 257 | background-image: url("./images/btn-prev.png"); 258 | } 259 | .ep-calendar-next-year { 260 | right: 0; 261 | 262 | background-image: url("./images/btn-next.png"); 263 | } 264 | 265 | .ep-calendar-menu-year { 266 | font-family: Tahoma,Verdana,宋体; 267 | font-size: 9pt; 268 | line-height: 20px; 269 | 270 | /* display: inline; */ 271 | 272 | float: left; 273 | 274 | width: 32px; 275 | height: 21px; 276 | margin: 0 1px 4px; 277 | padding: 1px; 278 | 279 | cursor: pointer; 280 | 281 | border: 1px solid transparent; 282 | -webkit-border-radius: 2px; 283 | -moz-border-radius: 2px; 284 | border-radius: 2px; 285 | } 286 | .ep-calendar-menu-year.selected, 287 | .ep-calendar-menu-year:hover { 288 | color: #fff; 289 | border-color: #f96d41; 290 | background: #f96d41; 291 | } 292 | .ep-calendar-menu-footer { 293 | position: absolute; 294 | bottom: 0; 295 | left: 0; 296 | 297 | width: 100%; 298 | height: 24px; 299 | padding: 0; 300 | padding-top: 8px; 301 | padding-bottom: 8px; 302 | 303 | text-align: center; 304 | 305 | border-top: 1px solid #e6e7eb; 306 | background: #f6f6f6; 307 | } 308 | .ep-calendar-okbtn, 309 | .ep-calendar-cancelbtn { 310 | line-height: 14px; 311 | 312 | display: inline-block; 313 | 314 | width: 52px; 315 | margin: 0 3px; 316 | padding: 5px 0; 317 | 318 | cursor: pointer; 319 | -webkit-transition: all .6s cubic-bezier(.175,.885,.32,1); 320 | -moz-transition: all .6s cubic-bezier(.175,.885,.32,1); 321 | -o-transition: all .6s cubic-bezier(.175,.885,.32,1); 322 | transition: all .6s cubic-bezier(.175,.885,.32,1); 323 | 324 | color: #fff; 325 | border: 1px solid transparent; 326 | -webkit-border-radius: 2px; 327 | -moz-border-radius: 2px; 328 | border-radius: 2px; 329 | } 330 | .ep-calendar-okbtn { 331 | border-color: #f96d41; 332 | background-color: #f96d41; 333 | } 334 | .ep-calendar-okbtn:hover { 335 | border-color: #f97e41; 336 | background-color: #f97e41; 337 | } 338 | .ep-calendar-cancelbtn { 339 | border-color: #59c2e6; 340 | background-color: #59c2e6; 341 | } 342 | .ep-calendar-cancelbtn:hover { 343 | border-color: #51b6d9; 344 | background-color: #51b6d9; 345 | } 346 | -------------------------------------------------------------------------------- /component/calendar/calendar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 月视图视图日历 3 | */ 4 | /* global epctrl,moment */ 5 | 6 | epctrl.Calendar = function (cfg) { 7 | epctrl.Calendar.superclass.constructor.apply(this, arguments); 8 | 9 | // 添加控件的css和js资源 如果是在父类的基础上新增css资源 则使用以下两个方法 10 | // this.addCss(); 11 | // this.addJs(); 12 | // 如果要给当前控件新的css、js资源,则通过this.setjs()和this.setCss()进行配置,参数为一个字符串或数组。 13 | this.setCss('calendar/calendar.css'); 14 | 15 | // 当前无moment时 自动引入moment 16 | if (!window.moment || typeof window.moment != 'function') { 17 | this.setJs('../lib/moment.min.js'); 18 | } 19 | 20 | // 加入传递的配置 21 | this.cfg = cfg || {}; 22 | }; 23 | 24 | epctrl.extend('Calendar', 'Control', { 25 | // 控件类型 必须指定,值即为控件的名称 26 | type: 'Calendar', 27 | // 选中日期的样式类名 28 | selectedCls: 'ep-calendar-selected', 29 | 30 | _defaultCfg: { 31 | // 不指定时 占满指定元素的宽高度 32 | width: '', 33 | height: '', 34 | 35 | // 初始化渲染的年月 格式如:'2017-9' 36 | date: '', 37 | // 是否显示日历外边框 38 | showBorder: true, 39 | 40 | // 设置的选中日期不在当前视图中时,是否自动重绘 41 | autoReRender: true, 42 | // 星期从周日开始? 43 | dayStartFromSunday: false, 44 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调 45 | headerHeight: '', 46 | // 日历底部高度,用不到可以不用管 47 | footerHeight: '', 48 | // 日历年月选择弹窗的宽高 49 | menuHeigt: 240, 50 | menuWidth: 220 51 | }, 52 | 53 | // 初始化必要结构 54 | _initContainer: function () { 55 | this.$container.empty(); 56 | 57 | // 整个日历 58 | var el = document.createElement('div'); 59 | el.className = 'ep-calendar'; 60 | 61 | this.cfg.width && (el.style.width = this.cfg.width); 62 | this.cfg.height && (el.style.height = this.cfg.height); 63 | 64 | if (!this.showBorder) { 65 | el.style.border = 'none'; 66 | } 67 | 68 | this.el = el; 69 | 70 | // 头部 71 | var header = document.createElement('div'); 72 | header.className = 'ep-calendar-header'; 73 | header.innerHTML = '
'; 74 | 75 | this.cfg.headerHeight && (header.style.height = this.cfg.headerHeight); 76 | 77 | this._header = header; 78 | this._headerLeft = header.firstChild; 79 | this._headerRight = header.lastChild; 80 | // this._headerCenter = jQuery('.ep-weekcalendar-header-center', header)[0]; 81 | this._headerCenter = this._headerLeft.nextSibling; 82 | // this._prevBtn = jQuery('.ep-weekcalendar-header-btn-prev', this._headerCenter)[0]; 83 | this._prevBtn = this._headerCenter.firstChild; 84 | this._titleEl = this._prevBtn.nextSibling; 85 | 86 | this._nextBtn = this._headerCenter.lastChild; 87 | this.el.appendChild(header); 88 | 89 | // 主体 90 | var body = document.createElement('table'), 91 | thead = document.createElement('thead'), 92 | tbody = document.createElement('tbody'), 93 | tr = document.createElement('tr'); 94 | 95 | body.className = 'ep-calendar-body'; 96 | tr.className = 'ep-calendar-daysheader'; 97 | tbody.className = 'ep-calendar-days'; 98 | 99 | thead.appendChild(tr); 100 | body.appendChild(thead); 101 | body.appendChild(tbody); 102 | 103 | // innerHTML IE8下无法操作 104 | // body.innerHTML = ''; 105 | 106 | // this._daysHeader = jQuery('.ep-calendar-daysheader', body)[0]; 107 | // this._daysBody = jQuery('.ep-calendar-days', body)[0]; 108 | 109 | this._daysHeader = tr; 110 | this._daysBody = tbody; 111 | 112 | this.el.appendChild(body); 113 | this._body = body; 114 | 115 | // 底部 116 | var footer = document.createElement('div'); 117 | footer.className = 'ep-calendar-footer'; 118 | 119 | this.cfg.footerHeight && (header.style.height = this.cfg.footerHeight); 120 | 121 | this.el.appendChild(footer); 122 | this._footer = footer; 123 | 124 | // 年月选择弹出面板 125 | var menu = document.createElement('div'); 126 | menu.className = 'ep-calendar-menu'; 127 | 128 | this.cfg.menuHeigt && (menu.style.height = this.cfg.menuHeigt); 129 | this.cfg.menuWidth && (menu.style.menuWidth = this.cfg.menuWidth); 130 | this.menu = menu; 131 | this.el.appendChild(menu); 132 | 133 | this.container.appendChild(this.el); 134 | 135 | this._calcAndSetHeight(); 136 | }, 137 | // 计算并设置日历主体部分高度 138 | _calcAndSetHeight: function () { 139 | var head_h = jQuery(this._header).outerHeight(), 140 | footer_h = jQuery(this._footer).outerHeight(), 141 | body_h = jQuery(this.el).height() - head_h - footer_h; 142 | 143 | this._body.style.height = body_h + 'px'; 144 | }, 145 | 146 | _weekHeaderDays: { 147 | Sun: ['日', '一', '二', '三', '四', '五', '六'], 148 | Sun_full: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'], 149 | Sun_en: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'], 150 | Sun_en_full: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thurday', 'Friday', 'Saturday '], 151 | 152 | Mon: ['一', '二', '三', '四', '五', '六', '日'], 153 | Mon_full: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], 154 | Mon_en: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'], 155 | Mon_en_full: ['Monday', 'Tuesday', 'Wednesday', 'Thurday', 'Friday', 'Saturday', 'Sunday'] 156 | }, 157 | 158 | // 渲染头部的星期 159 | _renderWeek: function () { 160 | var weekDays = this.dayStartFromSunday ? this._weekHeaderDays.Sun : this._weekHeaderDays.Mon; 161 | 162 | for (var i = 0; i < 7; ++i) { 163 | 164 | var cell = document.createElement('th'), 165 | cellInner = document.createElement('div'), 166 | text = document.createElement('span'), 167 | isoWeekday = this.dayStartFromSunday ? (i === 0 ? 7 : i) : i + 1; 168 | 169 | cell.className = 'ep-calendar-day'; 170 | cellInner.className = 'ep-calendar-day-inner'; 171 | // iso星期 周一至周日为1-7 172 | cell.setAttribute('data-isoweekday', isoWeekday); 173 | cellInner.appendChild(text); 174 | cell.appendChild(cellInner); 175 | 176 | // 单元格渲染事件 还未插入页面 177 | var renderEv = this.fire('cellRender', { 178 | // isoweekday 周一至周日为1-7 179 | isoWeekday: isoWeekday, 180 | // 星期th的class 181 | dayCls: 'ep-calendar-day-text', 182 | // 星期的内容 183 | dayText: weekDays[i], 184 | // 日历dom 185 | el: this.el, 186 | // 当前单元格dom 187 | tdEl: cell, 188 | // 额外注入的html 189 | extraHtml: '', 190 | // 是否表格头 即是否为头部星期几 191 | isHeader: true 192 | }); 193 | 194 | // 对修改的处理 195 | text.className = renderEv.dayCls; 196 | text.innerText = renderEv.dayText; 197 | 198 | // 添加新增内容 199 | if (renderEv.extraHtml) { 200 | jQuery(renderEv.extraHtml).appendTo(cellInner); 201 | } 202 | 203 | this._daysHeader.appendChild(cell); 204 | 205 | // 单元格渲染事件 206 | this.fire('afterCellRender', { 207 | isoWeekday: isoWeekday, 208 | dayCls: text.className, 209 | dayText: text.innerText, 210 | el: this.el, 211 | isHeader: true, 212 | extraHtml: renderEv.extraHtml, 213 | tdEl: cell 214 | }); 215 | } 216 | }, 217 | 218 | _renderTitle: function () { 219 | this._titleEl.innerHTML = moment(this.currMonth, 'YYYY-MM').format('YYYY年MM月'); 220 | this.fire('titleRender', { 221 | titleEl: this._titleEl, 222 | currMonth: this.currMonth 223 | }); 224 | }, 225 | 226 | // 清空已渲染的日历 227 | _clearDays: function () { 228 | var trs = this._daysBody.childNodes; 229 | 230 | for (var i = trs.length; i > 0; --i) { 231 | this._daysBody.removeChild(trs[i - 1]); 232 | } 233 | }, 234 | // 每天的渲染 235 | _renderDay: function (date, currTr) { 236 | var td = document.createElement('td'), 237 | tdInner = document.createElement('div'), 238 | text = document.createElement('span'), 239 | day = date.isoWeekday(), 240 | // 返回的月份是0-11 241 | month = date.month() + 1; 242 | 243 | tdInner.appendChild(text); 244 | td.appendChild(tdInner); 245 | 246 | td.className = 'ep-calendar-date'; 247 | tdInner.className = 'ep-calendar-date-inner'; 248 | // 完整日期 249 | td.setAttribute('data-date', date.format('YYYY-MM-DD')); 250 | // 对应的iso星期 251 | td.setAttribute('data-isoweekday', day); 252 | // text.className = 'ep-calendar-date-text'; 253 | // text.innerText = date.date(); 254 | 255 | // 周末标记text.className 256 | if (day === 6 || day === 7) { 257 | td.className += ' ep-calenday-weekend'; 258 | } 259 | // 非本月标记 260 | // substr 在ie8下有问题 261 | // if (month != parseInt(this.currMonth.substr(-2))) { 262 | if (month != parseInt(this.currMonth.substr(5), 10)) { 263 | td.className += ' ep-calendar-othermonth'; 264 | } 265 | // 今天标记 266 | if (this.today == date.format('YYYY-MM-DD')) { 267 | td.className += ' ep-calendar-today'; 268 | } 269 | 270 | // 每天渲染时发生 还未插入页面 271 | var renderEvent = this.fire('cellRender', { 272 | // 当天的完整日期 273 | date: date.format('YYYY-MM-DD'), 274 | // 当天的iso星期 275 | isoWeekday: day, 276 | // 日历dom 277 | el: this.el, 278 | // 当前单元格 279 | tdEl: td, 280 | // 日期文本 281 | dateText: date.date(), 282 | // 日期class 283 | dateCls: 'ep-calendar-date-text', 284 | // 需要注入的额外的html 285 | extraHtml: '', 286 | 287 | isHeader: false 288 | }); 289 | 290 | // 处理对dayText内容和样式的更改 291 | text.innerText = renderEvent.dateText; 292 | text.className = renderEvent.dateCls; 293 | 294 | // 添加新增内容 295 | if (renderEvent.extraHtml) { 296 | jQuery(renderEvent.extraHtml).appendTo(tdInner); 297 | } 298 | 299 | currTr.appendChild(renderEvent.tdEl); 300 | 301 | // 每天渲染后发生 插入到页面 302 | this.fire('afterCellRender', { 303 | date: date.format('YYYY-MM-DD'), 304 | isoWeekday: day, 305 | el: this.el, 306 | tdEl: td, 307 | dateText: text.innerText, 308 | dateCls: text.className, 309 | extraHtml: renderEvent.extraHtml, 310 | isHeader: false 311 | }); 312 | }, 313 | 314 | // 日历可变部分的渲染 315 | _render: function () { 316 | this._initStartEnd(); 317 | 318 | var weeks = 6, 319 | days = 7, 320 | curDate = this.startDateOfMonth.clone(), 321 | tr; 322 | 323 | var dtd = jQuery.Deferred(), 324 | start = this.startDateOfMonth.format('YYYY-MM-DD'), 325 | end = this.endDateOfMonth.format('YYYY-MM-DD'); 326 | 327 | // 日期渲染前触发渲染前事件 328 | // 如果需要动态获取数据则将获取数据的ajax加入到事件对象的ajax属性中 329 | var ev = this.fire('beforeDateRender', { 330 | momth: this.currMonth, 331 | startDate: start, 332 | endDate: end, 333 | ajax: null 334 | }); 335 | 336 | // 没有用户加入的ajax 则直接模拟一个成功的ajax 337 | if (!ev.ajax) { 338 | ev.ajax = dtd.promise(); 339 | dtd.resolve(); 340 | } 341 | 342 | var me = this; 343 | 344 | ev.ajax.then(function () { 345 | // 拿到数据后再清空 并开始新的渲染 346 | me._clearDays(); 347 | me._renderTitle(); 348 | 349 | for (var i = 0; i < weeks; ++i) { 350 | tr = document.createElement('tr'); 351 | tr.className = 'ep-calendar-week'; 352 | me._daysBody.appendChild(tr); 353 | 354 | for (var j = 0; j < days; ++j) { 355 | // 渲染一天 并递增 356 | me._renderDay(curDate, tr); 357 | curDate.add(1, 'day'); 358 | } 359 | } 360 | 361 | // 全部日期渲染完成 362 | me.fire('afterDateRender', { 363 | momth: me.currMonth, 364 | startDate: start, 365 | endDate: end, 366 | ajax: ev.ajax 367 | }); 368 | }); 369 | }, 370 | 371 | render: function (ym) { 372 | // 如果指定了渲染月份 进行处理 373 | if (ym) { 374 | var date = moment(ym, 'YYYY-MM'); 375 | if (date.isValid()) { 376 | this.currMonth = date.format('YYYY-MM'); 377 | } else { 378 | this.throwError(ym + '指定的日期不正确!'); 379 | } 380 | } 381 | 382 | // 未初始化时先初始化不变的结构 383 | if (!this._isInit) { 384 | this._initContainer(); 385 | this._renderWeek(); 386 | this._isInit = true; 387 | } 388 | 389 | this._render(); 390 | 391 | }, 392 | 393 | // 强制重新渲染 394 | reRedner: function () { 395 | if (!this._isInit) { 396 | this.throwError('日历还未渲染过,无需重新渲染!'); 397 | } 398 | 399 | this._initContainer(); 400 | this._renderWeek(); 401 | this.render(); 402 | }, 403 | 404 | // 初始化当前月份的开始日期和结束日期 405 | _initStartEnd: function () { 406 | // 当月1号 407 | var currMonth = moment(this.currMonth, 'YYYY-MM'), 408 | // 当月1号是周几 the ISO day of the week with 1 being Monday and 7 being Sunday. 409 | firstDay_weekday = currMonth.isoWeekday(), 410 | startDateOfMonth, 411 | endDateOfMonth; 412 | if (!this.dayStartFromSunday) { 413 | // 开始为周一 则向前减少周几的天数-1即为 开始的日期 414 | startDateOfMonth = currMonth.subtract(firstDay_weekday - 1, 'day'); 415 | } else { 416 | // 开始为周日 则直接向前周几的天数即可 417 | startDateOfMonth = currMonth.subtract(firstDay_weekday, 'day'); 418 | } 419 | 420 | endDateOfMonth = startDateOfMonth.clone().add(41, 'day'); 421 | 422 | this.startDateOfMonth = startDateOfMonth; 423 | this.endDateOfMonth = endDateOfMonth; 424 | }, 425 | 426 | // 设置月份 427 | setMonth: function (ym) { 428 | var date = moment(ym, 'YYYY-MM'); 429 | 430 | if (date.isValid()) { 431 | var oldMonth = this.currMonth, 432 | aimMonth = date.format('YYYY-MM'); 433 | 434 | // 月份变动前 435 | this.fire('beforeMonthChange', { 436 | el: this.el, 437 | oldMonth: oldMonth, 438 | newMonth: aimMonth 439 | }); 440 | 441 | this.currMonth = aimMonth; 442 | this.render(); 443 | 444 | // 月份变动后 445 | this.fire('afterMonthChange', { 446 | el: this.el, 447 | oldMonth: oldMonth, 448 | newMonth: aimMonth 449 | }); 450 | 451 | } else { 452 | throw new Error(ym + '是一个不合法的日期'); 453 | } 454 | }, 455 | getMonth: function () { 456 | return this.currMonth; 457 | }, 458 | // 上一月 459 | prevMonth: function () { 460 | var prevDate = moment(this.currMonth, 'YYYY-MM').subtract(1, 'M'); 461 | this.setMonth(prevDate); 462 | }, 463 | // 下一月 464 | nextMonth: function () { 465 | var nextDate = moment(this.currMonth, 'YYYY-MM').add(1, 'M'); 466 | this.setMonth(nextDate); 467 | }, 468 | 469 | // 设置选中的日期 470 | setSelected: function (ymd) { 471 | // 是否只输入了一个仅包含日的日期,此时将仅在当前月中查找 472 | var isCurrMonthDate = /^\d{1,2}$/.test(ymd), 473 | // 所有日期 474 | $days = jQuery(this._daysBody).find('.ep-calendar-date'), 475 | // 目标日期 476 | $day, 477 | date; 478 | 479 | if (isCurrMonthDate) { 480 | date = moment(this.currMonth + '-' + ymd, 'YYYY-MM-DD'); 481 | 482 | if (!date.isValid()) { 483 | this.throwError('日期不合法'); 484 | } 485 | 486 | // $day = $days.filter('[data-date="' + date.format('YYYY-MM-DD') + '"]'); 487 | $day = this._getTd(date, $days); 488 | 489 | if ($day.length) { 490 | $days.removeClass(this.selectedCls); 491 | $day.addClass(this.selectedCls); 492 | } else { 493 | this.throwError('当前月中没有' + ymd + '这一天!'); 494 | } 495 | 496 | return; 497 | } 498 | 499 | // 否则则应该是一个完整的日期 500 | date = moment(ymd, 'YYYY-MM-DD'); 501 | 502 | if (date.isValid()) { 503 | 504 | // $day = $days.filter('[data-date="' + dateStr + '"]'); 505 | $day = this._getTd(date, $days); 506 | 507 | if ($day.length) { 508 | // 有则直接显示 509 | $days.removeClass(this.selectedCls); 510 | $day.addClass(this.selectedCls); 511 | } else if (this.autoReRender) { 512 | // 不在 但是支持自动重绘 则渲染并设置 513 | this.render(date.format('YYYY-MM')); 514 | this.setSelected(ymd); 515 | } else { 516 | // 日期不存在 且 不自动重绘时 抛出错误 517 | this.throwError('指定日期不在当前视图,且未开启自动切换,无法设置!'); 518 | } 519 | 520 | } else { 521 | this.throwError(ymd + ',此日期不合法!'); 522 | } 523 | }, 524 | getSelected: function () { 525 | var selectedTd = jQuery('.' + this.selectedCls, this._daysBody)[0]; 526 | 527 | return selectedTd ? moment(selectedTd.getAttribute('data-date'), 'YYYY-MM-DD').format('YYYY-MM-DD') : ''; 528 | }, 529 | // 获取当前视图中的开始日期和结束日期 530 | getStartEnd: function () { 531 | return { 532 | start: this.startDateOfMonth.format('YYYY-MM-DD'), 533 | end: this.endDateOfMonth.format('YYYY-MM-DD') 534 | }; 535 | }, 536 | // 根据日期获取td 537 | _getTd: function (date, $days) { 538 | 539 | $days = $days || jQuery(this._daysBody).find('.ep-calendar-date'); 540 | 541 | return $days.filter('[data-date="' + date.format('YYYY-MM-DD') + '"]'); 542 | }, 543 | 544 | getELByDate: function (md) { 545 | 546 | var date; 547 | 548 | if (/^\d{1,2}$/.test(md)) { 549 | // 只提供一个日期 不包含月份,则在当前月中查找 550 | date = moment(this.currMonth + '-' + md, 'YYYY-MM-DD'); 551 | } else if (/^\d{1,2}-\d{1,2}$/.test(md)) { 552 | // 月-日 的形式 553 | date = moment((new Date()).getFullYear() + '-' + md, 'YYYY-MM-DD'); 554 | } else { 555 | // 否则直接认为是一个 完整日期 556 | date = moment(md, 'YYYY-MM-DD'); 557 | } 558 | 559 | // if (!date.isValid()) { 560 | // this.throwError(md + ',此日期不合法!'); 561 | // } 562 | 563 | var $days = this._getTd(date); 564 | 565 | return $days.length ? $days[0] : null; 566 | 567 | }, 568 | 569 | // 回到今天的视图 570 | goToday: function () { 571 | this.today = moment().format('YYYY-MM-DD'); 572 | return this.setMonth(this.today); 573 | }, 574 | 575 | // 回到初始化的视图 576 | reset: function () { 577 | return this.setMonth(this.cfg.date || new Date()); 578 | }, 579 | 580 | // 初始化事件 581 | _initEvent: function () { 582 | var my = this; 583 | jQuery(this.el) 584 | // 日期单元格 585 | .on('click', '.ep-calendar-date', function (e) { 586 | var date = this.getAttribute('data-date'), 587 | ev = my.fire('dayClick', { 588 | ev: e, 589 | date: date, 590 | day: this.getAttribute('data-isoweekday'), 591 | el: my.el, 592 | tdEl: this 593 | }); 594 | 595 | // 如果修改事件对象的cancel为true后 则不进行后续的选中操作 596 | if (!ev.cancel) { 597 | my.setSelected(date); 598 | } 599 | 600 | }) 601 | // 头部星期 602 | .on('click', '.ep-calendar-day', function (e) { 603 | my.fire('dayHeaderClick', { 604 | ev: e, 605 | day: this.getAttribute('data-isoweekday'), 606 | el: my.el, 607 | tdEl: this 608 | }); 609 | }) 610 | // 上一月 611 | .on('click', '.ep-calendar-btn-prev', function (e) { 612 | var ev = my.fire('prevBtnClick', { 613 | ev: e, 614 | currMonth: my.currMonth, 615 | el: my.el 616 | }); 617 | 618 | if (!ev.cancel) { 619 | my.prevMonth(); 620 | } 621 | }) 622 | // 下一月 623 | .on('click', '.ep-calendar-btn-next', function (e) { 624 | var ev = my.fire('nextBtnClick', { 625 | ev: e, 626 | currMonth: my.currMonth, 627 | el: my.el 628 | }); 629 | 630 | if (!ev.cancel) { 631 | my.nextMonth(); 632 | } 633 | }) 634 | // title 点击 635 | .on('click', '.ep-calendar-title', function (e) { 636 | // 已经显示则隐藏 637 | if (/ show/.test(my.menu.className)) { 638 | my.hideMenu(); 639 | return; 640 | } 641 | 642 | var ev = my.fire('beforeTitleClick', { 643 | ev: e, 644 | currMonth: my.currMonth, 645 | el: my.el 646 | }); 647 | 648 | if (!ev.cancel) { 649 | // showMonth 格式为"2017-10" 不指定时 选择面板渲染出当前年月 如果有指定 则渲染出的面板显示到能显示指定的年月 650 | my._onMenuShow(ev.showMonth); 651 | } 652 | }) 653 | // 年份、月份选择 654 | .on('click', '.ep-calendar-menu-month,.ep-calendar-menu-year', function () { 655 | 656 | jQuery(this).addClass('selected') 657 | .siblings('.selected').removeClass('selected'); 658 | }) 659 | // 确认 660 | .on('click', '.ep-calendar-okbtn', function () { 661 | var year = jQuery(my.menu_year).find('.ep-calendar-menu-year.selected').data('year'), 662 | month = jQuery(my.menu_month).find('.ep-calendar-menu-month.selected').data('month'); 663 | 664 | if (!year || !month) { 665 | my.hideMenu(); 666 | return; 667 | } 668 | 669 | my.setMonth(year + '-' + month); 670 | my.hideMenu(); 671 | }) 672 | // 取消 673 | .on('click', '.ep-calendar-cancelbtn', function () { 674 | my.hideMenu(); 675 | }) 676 | // 年份选择翻页 677 | .on('click', '.ep-calendar-next-year,.ep-calendar-prev-year', function () { 678 | var $this = jQuery(this), 679 | firstYear = parseInt(my.menu_year.firstChild.getAttribute('data-year'), 10), 680 | aimStartYear; 681 | 682 | if ($this.hasClass('ep-calendar-prev-year')) { 683 | aimStartYear = firstYear - 10; 684 | } else if ($this.hasClass('ep-calendar-next-year')) { 685 | aimStartYear = firstYear + 10; 686 | } 687 | 688 | my._renderYear(aimStartYear); 689 | }); 690 | // 空白处点击收起年月选择 691 | jQuery(document.body).on('click', function (e) { 692 | var $target = jQuery(e.target); 693 | 694 | if (!$target.closest('.ep-calendar-menu').length && !$target.closest('.ep-calendar-title').length) { 695 | my.hideMenu(); 696 | } 697 | }); 698 | }, 699 | hideMenu: function () { 700 | this.menu.className = this.menu.className.replace(' show', ''); 701 | }, 702 | 703 | showMenu: function (ym) { 704 | this._onMenuShow(ym); 705 | }, 706 | 707 | // 年月面板显示时 708 | _onMenuShow: function (ym) { 709 | ym = ym || this.currMonth; 710 | 711 | this.menu.className += ' show'; 712 | 713 | var temp = ym.split('-'), 714 | y = parseInt(temp[0], 10), 715 | m = parseInt(temp[1], 10); 716 | 717 | if (!this.menuIsInit) { 718 | this._initMenu(y, m); 719 | this.menuIsInit = true; 720 | } else { 721 | // 重新渲染年份 并重置月份的正确选中状态 722 | this._renderYear(y); 723 | jQuery(this.menu_month).find('[data-month="' + m + '"]').addClass('selected') 724 | .siblings().removeClass('selected'); 725 | } 726 | }, 727 | // 初始化年月选择 728 | _initMenu: function (y, m) { 729 | var month = document.createElement('div'), 730 | year = document.createElement('div'), 731 | yearInner = document.createElement('div'), 732 | footer = document.createElement('div'), 733 | okBtn = document.createElement('span'), 734 | cancelBtn = document.createElement('span'), 735 | prevYear = document.createElement('span'), 736 | nextYear = document.createElement('span'); 737 | 738 | month.className = 'ep-calendar-menu-months'; 739 | year.className = 'ep-calendar-menu-years'; 740 | yearInner.className = 'ep-calendar-menu-years-inner'; 741 | footer.className = 'ep-calendar-menu-footer'; 742 | okBtn.className = 'ep-calendar-okbtn'; 743 | okBtn.innerText = '确定'; 744 | cancelBtn.innerText = '取消'; 745 | cancelBtn.className = 'ep-calendar-cancelbtn'; 746 | prevYear.className = 'ep-calendar-prev-year'; 747 | nextYear.className = 'ep-calendar-next-year'; 748 | 749 | footer.appendChild(okBtn); 750 | footer.appendChild(cancelBtn); 751 | year.appendChild(yearInner); 752 | year.appendChild(prevYear); 753 | year.appendChild(nextYear); 754 | 755 | this.menu_month = month; 756 | this.menu_year = yearInner; 757 | 758 | this.menu.appendChild(month); 759 | this.menu.appendChild(year); 760 | this.menu.appendChild(footer); 761 | 762 | this._renderMonth(m); 763 | this._renderYear(y, y); 764 | }, 765 | 766 | // 渲染月份选择 767 | _renderMonth: function (currMonth) { 768 | 769 | var i = 1, 770 | months = this.menu_month, 771 | month = document.createElement('span'), 772 | node; 773 | 774 | month.className = 'ep-calendar-menu-month'; 775 | 776 | while (i < 13) { 777 | node = month.cloneNode(true); 778 | if (i == currMonth) { 779 | node.className += ' selected'; 780 | } 781 | node.innerText = i + '月'; 782 | node.setAttribute('data-month', i); 783 | 784 | months.appendChild(node); 785 | ++i; 786 | } 787 | 788 | }, 789 | 790 | // 渲染年份选择 791 | _renderYear: function (start, curr) { 792 | curr = curr || parseInt(this.currMonth.split('-')[0], 10); 793 | var i = 0, 794 | years = this.menu_year, 795 | year = document.createElement('span'), 796 | thisYear, 797 | node; 798 | years.innerHTML = ''; 799 | 800 | year.className += 'ep-calendar-menu-year'; 801 | 802 | while (i < 10) { 803 | node = year.cloneNode(); 804 | thisYear = start + i; 805 | 806 | if (thisYear === curr) { 807 | node.className += ' selected'; 808 | } 809 | node.innerText = thisYear; 810 | node.setAttribute('data-year', thisYear); 811 | years.appendChild(node); 812 | 813 | ++i; 814 | } 815 | }, 816 | 817 | // 初始化必要的配置 818 | initCfg: function () { 819 | // 将一些常用配置读出 820 | if (!this.cfg || !this.cfg.el) { 821 | throw new Error('必须指定日历渲染的html元素'); 822 | } 823 | 824 | // 合并默认配置 825 | this.cfg = jQuery.extend({}, this._defaultCfg, this.cfg); 826 | 827 | // 日历渲染容器 828 | this.$container = jQuery(this.cfg.el); 829 | this.container = this.$container[0]; 830 | 831 | // 当前显示的月份 优先取当前的指定的日期,没有则取今天 832 | 833 | this.currMonth = this.cfg.date ? moment(this.cfg.date, 'YYYY-MM').format('YYYY-MM') : moment().format('YYYY-MM'); 834 | 835 | 836 | // 今天 837 | this.today = moment().format('YYYY-MM-DD'); 838 | 839 | // 星期从周日开始? 840 | this.dayStartFromSunday = this.cfg.dayStartFromSunday; 841 | 842 | // 设置的选中日期不在当前视图中时,是否自动重绘 843 | this.autoReRender = this.cfg.autoReRender; 844 | 845 | // 是否显示日历外边框 846 | this.showBorder = this.cfg.showBorder; 847 | }, 848 | 849 | // 此方法在js等资源加载完成后自动调用 850 | create: function () { 851 | 852 | // 触发创建前事件 853 | this.fire('beforeCreate'); 854 | 855 | this.initCfg(); 856 | this.render(); 857 | 858 | this._initEvent(); 859 | 860 | this.fire('afterCreate'); 861 | } 862 | }); 863 | -------------------------------------------------------------------------------- /component/calendar/demo/calendar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 月视图日历 9 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /component/calendar/images/btn-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/images/btn-next.png -------------------------------------------------------------------------------- /component/calendar/images/btn-prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/images/btn-prev.png -------------------------------------------------------------------------------- /component/calendar/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/images/info.png -------------------------------------------------------------------------------- /component/calendar/readme.md: -------------------------------------------------------------------------------- 1 | # 日历控件 2 | 3 | 此日历控件是基于框架组件机制,自行实现的一个月视图日历,主要功能在于支持日历中每天的单元格中插入任意内容,支持异步获取内容。 4 | 5 | - [在线演示](https://cdswyda.github.io/component-frame/component/calendar/demo/calendar.html) 6 | - [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/calendar) 7 | 8 | ## 使用说明 9 | 10 | **资源引入** 11 | 12 | - 引入框架控件机制的基类js文件。 13 | 14 | - 引入此日历控件的js文件。 15 | 16 | **初始化** 17 | 18 | 使用框架控件机制的统一初始化方法 `epctrl.init(controlName, cfg)` 即可,第一个参数为控件名称,此处即 `Calendar`,第二个参数为控件的配置。 19 | 20 | 使用如下所示: 21 | 22 | ```js 23 | var calendar = epctrl.init('Calendar', { 24 | el: '#calendar', 25 | date: '2017-09' 26 | }); 27 | ``` 28 | 29 | 详细配置如下: 30 | 31 | ```js 32 | { 33 | // 不指定时 占满指定元素的宽高度 34 | width: '', 35 | height: '', 36 | 37 | // 初始化渲染的年月 格式如:'2017-9' 38 | date: '', 39 | // 是否显示日历外边框 40 | showBorder: true, 41 | 42 | // 设置的选中日期不在当前视图中时,是否自动重绘为可展示目标日期的月份 43 | autoReRender: true, 44 | // 星期从周日开始? 45 | dayStartFromSunday: false, 46 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调 47 | headerHeight: '', 48 | // 日历底部高度,用不到可以不用管 49 | footerHeight: '', 50 | // 日历年月选择弹窗的宽高 51 | menuHeigt: 240, 52 | menuWidth: 220 53 | } 54 | ``` 55 | 56 | ## 方法说明 57 | 58 | 方法名| 作用 | 参数 | 返回 | 说明 59 | -- | -- | -- | -- | -- 60 | `setMonth(ym)` | 设置日历显示的月份 | 要显示的年月,值为一个字符串,格式 `YYYY-MM` | 设置日历中显示的月份,此过程中触发 `beforeMonthChange` 和 `beforeMonthChange` 事件。
输入一个不合法的日期将抛出错误。 61 | `getMonth()` | 获取当前显示的月份 | | 当前正在显示的月份,格式 `YYYY-MM` | 62 | `prevMonth()` | 上一月 | | | 63 | `nextMonth()` | 下一月 | | | 64 | `setSelected(ymd)` | 设置选中的日期 | 参数ymd为一个字符串,表示完整的日期,格式 `YYYY-MM-DD`
或直接指定当月日期(如当前视图为2017-10,设置选中1号,可直接传入1即可),则直接在当月日期中选择。 | | 传入日期不合法时将抛出错误。指定日期在当前显示的视图中不存在,且 `autoReRender` 为false,会抛出错误,无法选中。 65 | `getSelected` | 获取当前选中的日期 | | 当前选中的日期,格式 `YYYY-MM`
无选中日期时,返回 `""` | 66 | `getStartEnd` | 返回当前视图中的开始日期和结束日期 | | `{start: '2017-09-25', end: '2017-11-5'}` | 67 | `getELByDate(md)` | 根据日期获取对应的dom元素 | 日期字符串,格式 `[[YYYY-]MM-]DD` | `HTMLElement` or `null` | 参数可以直接指定当月日期,格式 `DD`。或 `MM-DD` 和 `YYYY-MM-DD` 的形式 68 | `goToday()` | 回到能显示今天的视图 | | | 69 | `reset()` | 回到初始化时的视图 | | | 70 | 71 | ## 事件说明 72 | 73 | ### beforeCreate 74 | 75 | 日历控件开始创建前触发 76 | 77 | ### afterCreate 78 | 79 | 日历控件创建完成后触发 80 | 81 | ### beforeDateRender 82 | 83 | 日历中可变部分(即日期部分)渲染前触发 84 | 85 | 事件参数的基本属性见控件机制中的描述,此事件参数为: 86 | 87 | ```js 88 | { 89 | // 要渲染的月份 90 | momth: String, 91 | // 此月渲染时的开始日期,格式YYYY-MM-DD 92 | startDate: String, 93 | // 此月份日期渲染的结束日期 94 | endDate: String, 95 | // ajax对象 96 | ajax: null 97 | } 98 | ``` 99 | 100 | 如渲染2017-11月份的日期,则 `month` 为 `'2017-11'`, `startDate` : `'2017-10-30'` , `endDate` : `'2017-12-10'` 。 其中开始日期和结束日期是和日历的周一开始和周日开始有关的,上述为周一开始。 101 | 102 | 如果需要动态获取数据,以便在日期渲染时使用,可将获取数据的ajax对象加到事件参数的ajax属性上。如果用户加入了ajax,则之后的日期渲染会等待此ajax请求完成后才会触发。 103 | 104 | 如: 105 | 106 | ```js 107 | // 日期部分渲染前 108 | testCalendar.on('beforeDateRender', function (e) { 109 | var startDate = e.startDate, 110 | endDate = e.endDate; 111 | // 如果需要动态获取数据 112 | // 则将获取数据的ajax加到事件对象的ajax属性上即可 113 | // 日期渲染的cellRender事件将在ajax成功获取数据后执行 114 | e.ajax = $.ajax({ 115 | url: 'getDateInfo.xxx', 116 | data: { 117 | start: startDate, 118 | end: endDate 119 | } 120 | }); 121 | }); 122 | ``` 123 | 124 | ### afterDateRender 125 | 126 | 日历中可变部分(即日期部分)渲染前触发 127 | 128 | 事件参数同 `beforeDateRender`。 129 | 130 | 如果用户指定了获取数据的ajax,则时间参数中的ajax即用获取数据的ajax,否则ajax属性会替换成一个jQuery中Deferred对象模拟的Promise。 131 | 132 | ### cellRender 133 | 134 | 日历中星期头和日期单元格渲染时触发,基本结构已经生成,还未插入页面。 135 | 136 | 事件参数的基本属性见控件机制中的描述,此事件参数为: 137 | 138 | ```js 139 | { 140 | // isoweekday 周一至周日为1-7 141 | isoWeekday: Number, 142 | // 星期th的class 日期渲染时时无此属性 143 | dayCls: String, 144 | // 日期td 的class 星期渲染时无此属性 145 | dateCls: String, 146 | // 星期的内容 日期渲染时时无此属性 147 | dayText: String, 148 | // 当天的完整日期,YYYY-MM-DD 星期渲染时无此属性 149 | date: String, 150 | // 日期文本 星期渲染时无此属性 151 | dateText: String, 152 | // 日历dom 153 | el: HTMLElement, 154 | // 当前单元格dom 155 | tdEl: HTMLElement, 156 | // 额外注入的html 此部分html将会被自动插入到内容中 157 | extraHtml: String, 158 | // 是否表格头 即是否为头部星期几 159 | isHeader: Boolean 160 | } 161 | ``` 162 | 163 | ### afterCellRender 164 | 165 | 一个单元格渲染完成后触发。基本结构以及cellRender中自定义的内容已经插入页面。 166 | 167 | 参数同cellRender。 168 | 169 | ### titleRender 170 | 171 | 日历头部的“YYYY年MM月”改变时触发,还未插入页面 172 | 173 | ```js 174 | { 175 | // title dom对象 176 | titleEl: HTMLElement, 177 | // 当前月份 格式 YYYY-MM 178 | currMonth: String, 179 | // 实际显示的内容,支持HTML片段,修改此值即可自定义title 180 | titleContent: String 181 | } 182 | ``` 183 | 184 | ### afterTitleRender 185 | 186 | 日历头部的“YYYY年MM月”改变完成时触发,已经修改到页面。 187 | 188 | ### beforeMonthChange 189 | 190 | 日历显示月份变化前触发 191 | 192 | ```js 193 | { 194 | // 日历dom 195 | el: HTMLElement, 196 | // 之前的月份 YYYY-MM 197 | oldMonth: String, 198 | // 新的月份 199 | newMonth: String 200 | } 201 | ``` 202 | 203 | ### afterMonthChange 204 | 205 | 日历显示月份变化后触发,参数同 `beforeMonthChange`。 206 | 207 | ### dayClick 208 | 209 | 日期单元格点击时触发 210 | 211 | ```js 212 | { 213 | // jq封装的事件参数 214 | ev: Object, 215 | // 当前日期 216 | date: String, 217 | // iso星期 218 | day: String, 219 | // 日历dom 220 | el: HTMLElement, 221 | // 当前单元格dom 222 | tdEl: HTMLElement 223 | } 224 | ``` 225 | 226 | 一个日期的点击,默认导致此日期选中。在事件中设置事件参数的 `cancel` 属性为 `true` 可以阻止此操作。 227 | 228 | ### dayHeaderClick 229 | 230 | 日历中头部星期单元格点击时触发 231 | 232 | ```js 233 | { 234 | // jq封装的事件参数 235 | ev: Object, 236 | // 此星期的iso星期 1-7 为周一至周日 237 | day: String, 238 | // 日历dom 239 | el: HTMLElement, 240 | // 当前单元格dom 241 | tdEl: HTMLElement 242 | } 243 | ``` 244 | 245 | ### prevBtnClick 和 nextBtnClick 246 | 247 | 前一月和后一月点击事件 248 | 249 | ```js 250 | { 251 | // jq封装的事件参数 252 | ev: Object, 253 | // 当前显示的月份 254 | currMonth: String, 255 | // 日历dom 256 | el: HTMLElement 257 | } 258 | ``` 259 | 260 | 默认操作为视图重新渲染为前一月或后一月,设置事件参数的 `cancel` 属性为 `true` 可以阻止此操作。 261 | 262 | ### titleClick 263 | 264 | 日历title(YYYY年MM月)点击时触发 265 | 266 | ```js 267 | { 268 | // jq封装的事件参数 269 | ev: Object, 270 | // 当前显示的月份 271 | currMonth: String, 272 | // 日历dom 273 | el: HTMLElement, 274 | // 年月选择面板中展示出的年月 YYYY-MM 275 | // 默认为空,展示后显示出当前年月 276 | showMonth: String 277 | } 278 | ``` 279 | 280 | 此事件后续默认操作为展示年月选择面板,设置事件参数的 `cancel` 属性为 `true` 可以阻止此操作。 281 | -------------------------------------------------------------------------------- /component/calendar/themes/grace/skins/default/skin.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/themes/grace/skins/default/skin.css -------------------------------------------------------------------------------- /component/weekcalendar/demo/test.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | Mock.setup({ 3 | timeout: '50-600' 4 | }); 5 | 6 | function getParams(query) { 7 | var o = {}; 8 | $.each(query.split('&'), function (i, item) { 9 | var r = item.split('='); 10 | 11 | o[r[0]] = r[1]; 12 | }); 13 | return o; 14 | } 15 | 16 | var timeRanges = [ 17 | ['07:00', '16:00'], 18 | ['08:00', '17:00'], 19 | ['09:00', '18:00'], 20 | ['10:00', '19:00'], 21 | ['05:00', '09:00'], 22 | ['08:00', '24:00'], 23 | ['08:33', '21:55'], 24 | ['17:44', '09:44'] 25 | ]; 26 | 27 | function random() { 28 | return (Math.random() * 17 >> 0).toString(16); 29 | } 30 | 31 | function getColor() { 32 | return '#' + random() + random() + random()+ random() + random() + random(); 33 | } 34 | 35 | function mockDate(range) { 36 | var s = range.start, 37 | e = range.end, 38 | dateArr = [], 39 | data = [], 40 | i = 0, 41 | temp; 42 | 43 | while (i < 7) { 44 | temp = moment(s, 'YYYY-MM-DD'); 45 | dateArr.push(temp.add(i, 'day').format('YYYY-MM-DD')); 46 | i++; 47 | } 48 | 49 | i = 0; 50 | while (i < 7) { 51 | data.push({ 52 | id:i, 53 | categoryId: 'cate-1', 54 | title: '用车', 55 | content: '', 56 | start: dateArr[i] + ' ' + timeRanges[i][0], 57 | end: dateArr[i] + ' ' + timeRanges[i][1], 58 | bgColor: getColor() 59 | }); 60 | i++; 61 | } 62 | 63 | data.push({ 64 | id:i++, 65 | categoryId: 'cate-1', 66 | title: '车', 67 | content: '', 68 | start: dateArr[0] + ' 00:00' , 69 | end: dateArr[0] + ' 08:00', 70 | bgColor: getColor() 71 | }); 72 | data.push({ 73 | id:i++, 74 | categoryId: 'cate-1', 75 | title: '车', 76 | content: '', 77 | start: dateArr[0] + ' 14:00' , 78 | end: dateArr[0] + ' 20:00', 79 | bgColor: getColor() 80 | }); 81 | 82 | data.push({ 83 | id:i++, 84 | categoryId: 'cate-2', 85 | title: '本周用车', 86 | content: '', 87 | start: dateArr[0] + ' ' + timeRanges[7][0], 88 | end: dateArr[5] + ' ' + timeRanges[7][1], 89 | bgColor: getColor() 90 | }); 91 | 92 | data.push({ 93 | id:i++, 94 | categoryId: 'cate-3', 95 | title: '本周用车2', 96 | content: '', 97 | start: dateArr[0] + ' 00:00', 98 | end: dateArr[6] + ' 24:00', 99 | bgColor: getColor() 100 | }); 101 | 102 | return data; 103 | } 104 | 105 | Mock.mock('./test.json', function (opt) { 106 | 107 | return mockDate(getParams(opt.body)); 108 | 109 | }); 110 | 111 | 112 | })(); -------------------------------------------------------------------------------- /component/weekcalendar/demo/test.json: -------------------------------------------------------------------------------- 1 | [{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-12 07:00","end":"2017-11-12 16:00","bgColor":"#ab9"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-13 08:00","end":"2017-11-13 17:00","bgColor":"#5d8"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-14 09:00","end":"2017-11-14 18:00","bgColor":"#43e"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-15 10:00","end":"2017-11-15 19:00","bgColor":"#696"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-16 05:00","end":"2017-11-16 09:00","bgColor":"#ba7"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-17 08:00","end":"2017-11-17 24:00","bgColor":"#2de"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-18 08:33","end":"2017-11-18 21:55","bgColor":"#ec8"},{"categoryId":"cate-2","title":"本周用车","content":"","start":"2017-11-12 17:44","end":"2017-11-17 09:44","bgColor":"#7cb"},{"categoryId":"cate-3","title":"本周用车2","content":"","start":"2017-11-12 00:00","end":"2017-11-18 24:00","bgColor":"#4d2"}] -------------------------------------------------------------------------------- /component/weekcalendar/demo/week.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 二维周视图日历 10 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /component/weekcalendar/images/btn-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/weekcalendar/images/btn-next.png -------------------------------------------------------------------------------- /component/weekcalendar/images/btn-prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/weekcalendar/images/btn-prev.png -------------------------------------------------------------------------------- /component/weekcalendar/readme.md: -------------------------------------------------------------------------------- 1 | # 周视图二维日历控件 2 | 3 | 此日历控件是基于框架控件机制,自行实现的一个周视图的二维日历。 4 | 5 | 主要特色在于: 6 | 7 | - 由分类和日期形成一个二维视图 8 | - 可以在二维视图中插入任意小部件(而且自动根据开始结束时间计算好了在视图中占据的位置) 9 | - 点击事件中获取当前分类和日期,并支持日期的范围选择。 10 | 11 | 可用于一周内的日程展示、会议室安排、车辆安排等。 12 | 13 | - [在线演示](https://cdswyda.github.io/component-frame/component/weekcalendar/demo/week.html) 14 | - [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/weekcalendar) 15 | 16 | ## 使用说明 17 | 18 | **资源引入** 19 | 20 | - 引入框架控件机制的基类js文件。 21 | 22 | - 引入此日历控件的js文件。 23 | 24 | **初始化** 25 | 26 | 使用框架控件机制的统一初始化方法 epctrl.init(controlName, cfg) 即可,第一个参数为控件名称,此处即 WeekCalendar,第二个参数为控件的配置。 27 | 28 | 使用如下所示: 29 | 30 | ```js 31 | var calendar = epctrl.init('WeekCalendar', { 32 | el: '#calendar', 33 | date: '2017-11-11' 34 | }); 35 | ``` 36 | 37 | 相关配置如下: 38 | 39 | ```js 40 | { 41 | // 日历渲染的容器 必须指定 值为一个jq选择器 42 | el: '#calendar', 43 | // 日历宽高度 不指定时 占满指定元素的宽高度 44 | width: '', 45 | height: '', 46 | 47 | // 初始化渲染的日期 格式如:'2017-11-10' 48 | // 初始化的周视图中必定能够显示此日期,不知道则取当天日期 49 | date: '', 50 | // 是否显示边框 51 | showBorder: true, 52 | 53 | // 星期从周日开始? 54 | dayStartFromSunday: false, 55 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调 56 | headerHeight: '', 57 | // 左侧分类标题 58 | categoryTitle: '', 59 | // 左侧分类数据 60 | // 格式如: 61 | // [{ 62 | // id: 'cate-1', 63 | // name: '大众', 64 | // content: '苏A00000' 65 | // }, { 66 | // id: 'cate-2', 67 | // name: '雷诺', 68 | // content: '苏A00001' 69 | // }, { 70 | // id: 'cate-3', 71 | // name: '红旗', 72 | // content: '苏A00003' 73 | // }] 74 | category: [], 75 | // 日期时间分隔符 默认为空 对应格式为 '2017-11-11 20:00' 76 | // 对于'2017-11-11T20:00' 这样的格式务必指定正确的日期和时间之间的分隔符 77 | dateTimeSplit: ' ', 78 | // 计算插入部件位置精度的单位 支持秒和分钟 即 second/minute 默认为 second 79 | posUnit: 'second' 80 | } 81 | ``` 82 | 83 | ## 方法说明 84 | 85 | 方法名 | 作用 | 参数 | 返回 86 | -- | -- | -- | -- 87 | `setCategoryTitle(title)` | 设置分类标题 | `title` 字符串类型,分类名称 | - 88 | `getCategoryTitle()` | 获取分类标题 | | 分类的标题 89 | `setCategory(data)` | 设置左侧分类数据,
设置后分类和网格部分将重绘 | `data` 一个数组,表示左侧分类的数据,详细格式见配置信息 | 90 | `getCategory()` | 获取分类数据 | | 分类数据的数组 91 | `getGridElByPos(i,j)` | 根据位置获取日历网格dom元素 | `i` 、 `j` 为一个数字,表示行列索引,从0开始 | 对应的 `HTMLElement` 或 `null` 92 | `setDate(dateText)` | 指定一个日期渲染到能显示此日期的日历视图 | `dateText` 表示日期的字符串,格式为 `'11-11'` 或 `'2017-11-11'` 不指定年份时则取今天所在年份| 93 | `prevWeek()` | 上一周 | | 94 | `nextWeek()` | 下一周 | | 95 | `goToday()` | 回到今天的视图,今天已经在显示则不处理 | | 今天已经在视图中时返回 `false` 96 | `addWidget(data)` | 在日历中新增一个小部件 | `data` 生成小部件的数据对象。
属性如下:
`id`: 部件id
`categoryId`: 分类id
`title`: 部件title
`content`: 部件内容 可选
`start`: 开始时间 日期格式为2017-11-11 时间格式为08:00:00 日期和时间之间的分隔符可通过`dateTimeSplit`自行配置。如:"2017-11-11 08:22:23"
`end`: 结束时间
`bgColor`: 部件背景色 可选,有默认背景色 | 分类不存在或者用户取消添加时,返回`false`,否则返回新增部件的 `HTMLElement` 97 | `removeWidget(id)` | 移除一个小部件 | `id` 小部件ID | 部件不存在时,返回 `false` ,否则返回移除后的部件 `HTMLElement`。 98 | `getStartEnd(dateStr)` | 指定一个日期,获取对应的周视图中的开始日期和结束日期 | `dateStr` 日期字符串 格式 "2017-11-11" | 一个对象,start属性为开始日期,end为结束日期 99 | 100 | ## 事件说明 101 | 102 | ### beforeCreate 103 | 104 | 日历控件开始创建前触发 105 | 106 | ### afterCreate 107 | 108 | 日历控件创建完成后触发 109 | 110 | ### categoryRender 111 | 112 | 分类渲染时触发,结构生成还未插入页面 113 | 114 | ```js 115 | { 116 | categoryEl: HTMLElement, // 当前分类的HTML元素 117 | titleEl: HTMLElement, // 此分类标题的HTML元素 118 | contentEl: HTMLElement // 此分类内容的HTML元素 119 | } 120 | ``` 121 | 122 | ### agterCategoryRender 123 | 124 | 分类渲染时触发,已经插入页面,参数同上。 125 | 126 | ### dateRender 127 | 128 | 日历日期渲染时发生,结构生成还未插入页面。 129 | 130 | ```js 131 | { 132 | // 当前完整日期 2017-11-11 133 | date: String, 134 | // iso星期 1-7 表示周一至周日 135 | isoWeekday: Nomber, 136 | // 显示的文本 默认为 周一 11-13 137 | dateText: String, 138 | // 当前日期don元素的className 139 | dateCls: String, 140 | // 日历el 141 | el: HTMLElement, 142 | // 当前el 143 | dateEl: HTMLElement 144 | } 145 | ``` 146 | 147 | 对 `dateText` 和 `dateCls` 的处理会自动更新到日历控件上。`dateCls`通常可以新增一个类,不能直接覆盖默认的类名。 148 | 149 | ### afterDateRender 150 | 151 | 日历日期渲染完成时发生,已经插入页面。参数同上。 152 | 153 | ### titleChanged 154 | 155 | 日历title改变时触发。 156 | 157 | ```js 158 | { 159 | startDate: String, // 当前视图开始日期 格式 YYYY-MM-DD 160 | endDate: String, // 结束日期 161 | // 显示的文本 默认形式为2017年11月13日-19日 或 2017年11月27日-12月3日 162 | title: String, 163 | // 日历title的dom元素 164 | titleEl: HTMLElement 165 | } 166 | ``` 167 | 168 | ### dateChanged 169 | 170 | 日历日期改变后触发 171 | 172 | ```js 173 | { 174 | startDate: String, // 开始日期 格式 YYYY-MM-DD 175 | endDate: String, // 结束日期 176 | categorys: Array // 分类id集合 177 | } 178 | ``` 179 | 180 | ### dateClick 181 | 182 | 日期点击时触发,仅指显示日期的7天。 183 | 184 | ```js 185 | { 186 | ev: Object, // 原始click事件对象 187 | date: String, // 日期 188 | isoWeekday: Number, // iso星期 189 | dateEl: HTMLElement // 此日期的dom元素 190 | } 191 | ``` 192 | 193 | ### categoryClick 194 | 195 | 分类点击时触发 196 | 197 | ```js 198 | { 199 | ev: Object, // 原始click事件对象 200 | categoryId:String, // 此分类的id 201 | title: String, // 此分类名称 202 | categoryIndex: Number // 此分类的索引 203 | } 204 | ``` 205 | 206 | ### prevBtnClick 207 | 208 | 上一周点击时触发 209 | 210 | ```js 211 | { 212 | ev: Object, // 原始click事件对象 213 | start: String, // 当前视图开始日期 格式 YYYY-MM-DD 214 | end: String // 结束日期 215 | } 216 | ``` 217 | 218 | 设置事件对象的 `cancel` 为 `true` 可取消跳转到上一周 219 | 220 | ### nextWeekClick 221 | 222 | 下一周点击时触发,参数同上。 223 | 224 | ### widgetClick 225 | 226 | 日历中部件点击时触发 227 | 228 | ```js 229 | { 230 | ev: Object, // 原始click事件对象 231 | categoryId: String, // 所属分类id 232 | widgetId: String, // 部件id 233 | start: String, // 此部件开始日期时间 234 | end: String, // 结束日期时间 235 | title: String // 部件名称 236 | } 237 | ``` 238 | 239 | ### cellClick 240 | 241 | 日历中分类和日期形成的网格点击时触发,按下鼠标移动再松开,则可以选择一段时间。 242 | 243 | ```js 244 | { 245 | // 分类id 246 | categoryId: String, 247 | // 开始日期 248 | startDate: String, 249 | // 结束日期 250 | endDate: String, 251 | // 行索引 252 | rowIndex: Number, 253 | // 列索引范围 254 | columnIndexs: Array 255 | } 256 | ``` 257 | 258 | ### widgetOccupied 259 | 260 | 添加部件和现有部件冲突(重叠)时发生。 261 | 262 | ```js 263 | { 264 | occupiedWidgets: Array, // 和当前添加部件发生冲突的部件的集合,每个成员为部件的 HTMLElement 265 | currWidget: HTMLElement, // 当前部件的HTMLElement 266 | widgetData: Object // 此部件数据 267 | } 268 | ``` 269 | 270 | 在此事件中可对发生重叠的部件进行处理,或直接设置其 `cancel` 为 `true` 取消当前的添加。 271 | 272 | ### widgetAdd 273 | 274 | 部件添加时触发,已经检测过是否重叠,还未插入页面。 275 | 276 | ```js 277 | { 278 | widgetId: String, // 部件id 279 | categoryId: String, // 所属分类id 280 | start: String, // 开始日期时间 281 | end: String, // 结束日期时间 282 | startPos: Number, // 开始时间距离左侧的百分比 283 | endPos: Number, // 结束时间换算后距离左侧的百分比 284 | widgetEl: HTMLElement // 部件HTMLElement 285 | } 286 | ``` 287 | 288 | 设置其 `cancel` 为 `true` 取消当前的添加。 289 | 290 | ### afterWidgetAdd 291 | 292 | 部件添加完成时触发,参数同上。 293 | -------------------------------------------------------------------------------- /component/weekcalendar/weekcalendar.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .ep-weekcalendar { 3 | position: relative; 4 | 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | 10 | /* 头部 */ 11 | 12 | .ep-weekcalendar-header { 13 | position: relative; 14 | 15 | height: 42px; 16 | margin-bottom: 10px; 17 | padding: 8px 16px 0; 18 | 19 | text-align: center; 20 | 21 | background-color: #e0f2ff; 22 | } 23 | 24 | .ep-weekcalendar-header:after { 25 | display: table; 26 | clear: both; 27 | 28 | content: ""; 29 | } 30 | 31 | .ep-weekcalendar-header-left { 32 | float: left; 33 | 34 | height: 100%; 35 | } 36 | 37 | .ep-weekcalendar-header-right { 38 | float: right; 39 | 40 | height: 100%; 41 | } 42 | 43 | .ep-weekcalendar-header-center { 44 | font-size: 14px; 45 | line-height: 30px; 46 | 47 | position: absolute; 48 | left: 50%; 49 | 50 | width: 300px; 51 | height: 30px; 52 | margin-left: -150px; 53 | } 54 | 55 | .ep-weekcalendar-header-btn { 56 | display: block; 57 | 58 | width: 30px; 59 | height: 30px; 60 | 61 | cursor: pointer; 62 | 63 | background-repeat: no-repeat; 64 | background-position: center center; 65 | } 66 | 67 | .ep-weekcalendar-header-btn-prev { 68 | float: left; 69 | 70 | background-image: url("./images/btn-prev.png"); 71 | } 72 | 73 | .ep-weekcalendar-header-btn-next { 74 | float: right; 75 | 76 | background-image: url("./images/btn-next.png"); 77 | } 78 | 79 | .ep-weekcalendar-title { 80 | display: inline-block; 81 | 82 | width: 230px; 83 | height: 100%; 84 | } 85 | 86 | 87 | /* body */ 88 | 89 | .ep-weekcalendar-body { 90 | position: absolute; 91 | top: 60px; 92 | right: 0; 93 | bottom: 0; 94 | left: 0; 95 | } 96 | 97 | 98 | /* 左侧分类 */ 99 | 100 | .ep-weekcalendar-category-area { 101 | position: absolute; 102 | top: 0; 103 | 104 | width: 200px; 105 | } 106 | 107 | .ep-weekcalendar-category-header { 108 | height: 36px; 109 | margin-right: 10px; 110 | padding-right: 18px; 111 | 112 | text-align: center; 113 | 114 | color: #fff; 115 | -webkit-border-radius: 0 18px 18px 0; 116 | -moz-border-radius: 0 18px 18px 0; 117 | border-radius: 0 18px 18px 0; 118 | background: #51a6ef; 119 | } 120 | 121 | .ep-weekcalendar-category-title { 122 | font-size: 14px; 123 | line-height: 36px; 124 | } 125 | 126 | .ep-weekcalendar-category-list { 127 | list-style: none; 128 | margin: 0; 129 | padding: 0; 130 | } 131 | 132 | .ep-weekcalendar-category { 133 | box-sizing: border-box; 134 | height: 50px; 135 | } 136 | 137 | .ep-weekcalendar-category .title, 138 | .ep-weekcalendar-category .content { 139 | display: block; 140 | 141 | box-sizing: border-box; 142 | height: 50%; 143 | padding-left: 20px; 144 | 145 | color: #333; 146 | } 147 | 148 | .ep-weekcalendar-category .content { 149 | color: #888; 150 | } 151 | 152 | 153 | /* 日历中间 */ 154 | 155 | .ep-weekcalendar-time-area { 156 | min-height: 100px; 157 | margin-left: 200px; 158 | 159 | background: #fdfdfd; 160 | } 161 | 162 | 163 | /* 头部星期 */ 164 | 165 | .ep-weekcalendar-weeks { 166 | line-height: 36px; 167 | 168 | -webkit-box-sizing: border-box; 169 | -moz-box-sizing: border-box; 170 | box-sizing: border-box; 171 | height: 36px; 172 | 173 | text-align: center; 174 | } 175 | 176 | .ep-weekcalendar-weeks:after { 177 | display: table; 178 | clear: both; 179 | 180 | content: ""; 181 | } 182 | 183 | .ep-weekcalendar-week { 184 | float: left; 185 | overflow: hidden; 186 | 187 | -webkit-box-sizing: border-box; 188 | -moz-box-sizing: border-box; 189 | box-sizing: border-box; 190 | width: 14.2857%; 191 | 192 | white-space: nowrap; 193 | text-overflow: ellipsis; 194 | 195 | -o-text-overflow: ellipsis; 196 | } 197 | 198 | .ep-weekcalendar-week.weekend { 199 | background: #f2f2f2; 200 | } 201 | 202 | .ep-weekcalendar-main { 203 | position: relative; 204 | } 205 | 206 | 207 | /* 网格 */ 208 | 209 | .ep-weekcalendar-grid { 210 | box-sizing: border-box; 211 | margin-top: -1px; 212 | } 213 | 214 | .ep-weekcalendar-grid-row { 215 | height: 50px; 216 | } 217 | 218 | .ep-weekcalendar-grid-row:after { 219 | display: table; 220 | clear: both; 221 | 222 | content: ""; 223 | } 224 | 225 | .ep-weekcalendar-grid-item { 226 | display: block; 227 | float: left; 228 | 229 | box-sizing: border-box; 230 | width: 14.2857%; 231 | height: 100%; 232 | } 233 | 234 | .ep-weekcalendar-grid-item.weekend { 235 | background: #f2f2f2; 236 | } 237 | 238 | .ep-weekcalendar-grid-item.selected { 239 | background: #eaf8fa; 240 | } 241 | 242 | 243 | /* content */ 244 | 245 | .ep-weekcalendar-content { 246 | line-height: 24px; 247 | 248 | position: absolute; 249 | z-index: 1; 250 | top: 0; 251 | right: 0; 252 | bottom: 0; 253 | left: 0; 254 | } 255 | 256 | .ep-weekcalendar-content-row { 257 | position: relative; 258 | 259 | overflow: hidden; 260 | 261 | box-sizing: border-box; 262 | height: 50px; 263 | } 264 | 265 | .ep-weekcalendar-content-widget { 266 | position: absolute; 267 | 268 | box-sizing: border-box; 269 | height: 100%; 270 | padding: 0 5px; 271 | 272 | color: #fff; 273 | background-color: #6bb2ee; 274 | } 275 | 276 | .ep-weekcalendar.border { 277 | /* 日历网格 */ 278 | /* 内容行也需要一个透明边框 防止被内容盖住边框*/ 279 | } 280 | 281 | .ep-weekcalendar.border .ep-weekcalendar-category-list { 282 | border-left: 1px solid #ddd; 283 | } 284 | 285 | .ep-weekcalendar.border .ep-weekcalendar-category { 286 | border-bottom: 1px solid #ddd; 287 | } 288 | 289 | .ep-weekcalendar.border .ep-weekcalendar-weeks { 290 | line-height: 34px; 291 | 292 | border: 1px solid #ddd; 293 | border-right: none; 294 | } 295 | 296 | .ep-weekcalendar.border .ep-weekcalendar-week { 297 | border-right: 1px solid #ddd; 298 | } 299 | 300 | .ep-weekcalendar.border .ep-weekcalendar-grid { 301 | border-right: 1px solid #ddd; 302 | border-bottom: 1px solid #ddd; 303 | } 304 | 305 | .ep-weekcalendar.border .ep-weekcalendar-grid-item { 306 | border-top: 1px solid #ddd; 307 | border-left: 1px solid #ddd; 308 | } 309 | 310 | .ep-weekcalendar.border .ep-weekcalendar-content-row { 311 | border: 1px solid transparent; 312 | border-bottom: none; 313 | } 314 | 315 | 316 | /*# sourceMappingURL=weekcalendar.css.map */ 317 | -------------------------------------------------------------------------------- /component/weekcalendar/weekcalendar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 二维周视图日历 3 | * author: chends 4 | */ 5 | /* global epctrl,moment*/ 6 | 7 | epctrl.WeekCalendar = function (cfg) { 8 | epctrl.WeekCalendar.superclass.constructor.apply(this, arguments); 9 | 10 | // 添加控件的css和js资源 如果是在父类的基础上新增css资源 则使用以下两个方法 11 | // this.addCss(); 12 | // this.addJs(); 13 | // 如果要给当前控件新的css、js资源,则通过this.setjs()和this.setCss()进行配置,参数为一个字符串或数组。 14 | this.setCss('weekcalendar/weekcalendar.css'); 15 | 16 | // 当前无moment时 自动引入moment 17 | if (!window.moment || typeof window.moment != 'function') { 18 | this.setJs('../lib/moment.min.js'); 19 | } 20 | 21 | // 加入传递的配置 22 | this.cfg = cfg || {}; 23 | }; 24 | 25 | epctrl.extend('WeekCalendar', 'Control', { 26 | // 控件类型 必须指定,值即为控件的名称 27 | type: 'WeekCalendar', 28 | 29 | // 选中的单元格的class 30 | _selectedCls: 'selected', 31 | 32 | // 一周分钟数 33 | _WEEKMINUTES: 7 * 24 * 60, 34 | // 一周秒数 35 | _WEEKSECONDS: 7 * 24 * 3600, 36 | // 一天的分钟数秒数 37 | _DAYMINUTES: 24 * 60, 38 | _DAYSCONDS: 24 * 3600, 39 | 40 | // 计算位置的精度 取值second 或 minute 41 | posUnit: 'second', 42 | 43 | // 日期和时间之前的分隔符 44 | _dateTimeSplit: ' ', 45 | 46 | _defaultCfg: { 47 | // 不指定时 占满指定元素的宽高度 48 | width: '', 49 | height: '', 50 | 51 | // 初始化渲染的日期 格式如:'2017-11-10' 52 | date: '', 53 | // 是否显示边框 54 | showBorder: true, 55 | 56 | // 星期从周日开始? 57 | dayStartFromSunday: false, 58 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调 59 | headerHeight: '', 60 | // 左侧分类标题 61 | categoryTitle: '', 62 | // 左侧分类数据 63 | category: [] 64 | }, 65 | 66 | // 初始化配置 67 | _initCfg: function () { 68 | this.cfg = jQuery.extend({}, this._defaultCfg, this.cfg); 69 | 70 | if (!this.cfg.el) { 71 | this.throwError('必须指定日历渲染容器'); 72 | } 73 | 74 | // 日历渲染容器 75 | this.$container = jQuery(this.cfg.el); 76 | this.container = this.$container[0]; 77 | 78 | // 星期从周日开始? 79 | this.dayStartFromSunday = this.cfg.dayStartFromSunday; 80 | 81 | // 是否显示边框 82 | this.showBorder = this.cfg.showBorder; 83 | 84 | // 今天 85 | this.today = moment().format('YYYY-MM-DD'); 86 | 87 | // 需要展示的日期 88 | this.date = this.cfg.date || this.today; 89 | 90 | // 分类标题 91 | this._categoryTitle = this.cfg.categoryTitle; 92 | 93 | // 分类数据 94 | this._categoryData = this.cfg.category; 95 | 96 | // 日期时间分隔符 97 | this._dateTimeSplit = this.cfg.dateTimeSplit || this._dateTimeSplit; 98 | // 位置精度 99 | this.posUnit = this.cfg.posUnit || this.posUnit; 100 | }, 101 | 102 | // 渲染 103 | render: function () { 104 | 105 | this._initContainer(); 106 | 107 | this._renderCatagories(); 108 | 109 | this._renderChanged(); 110 | // this._renderWeeks(); 111 | // this._renderGrid(); 112 | // this._rednerContent(); 113 | 114 | }, 115 | 116 | // 初始化结构 117 | _initContainer: function () { 118 | this.$container.empty(); 119 | 120 | var el = document.createElement('div'); 121 | el.className = 'ep-weekcalendar' + (this.showBorder ? ' border' : ''); 122 | 123 | this.cfg.width && (el.style.width = this.cfg.width); 124 | this.cfg.height && (el.style.height = this.cfg.height); 125 | 126 | this.el = el; 127 | 128 | // header 129 | var header = document.createElement('div'); 130 | header.className = 'ep-weekcalendar-header'; 131 | header.innerHTML = '
'; 132 | 133 | this.cfg.headerHeight && (header.style.height = this.cfg.headerHeight); 134 | 135 | this._headerEl = header; 136 | 137 | this._headerLeft = header.firstChild; 138 | this._headerRight = header.lastChild; 139 | // this._headerCenter = jQuery('.ep-weekcalendar-header-center', header)[0]; 140 | this._headerCenter = this._headerLeft.nextSibling; 141 | // this._prevBtn = jQuery('.ep-weekcalendar-header-btn-prev', this._headerCenter)[0]; 142 | this._prevBtn = this._headerCenter.firstChild; 143 | this._titleEl = this._prevBtn.nextSibling; 144 | 145 | this._nextBtn = this._headerCenter.lastChild; 146 | this.el.appendChild(header); 147 | 148 | // 主体部分 左侧分类 右侧日历 149 | var body = document.createElement('div'); 150 | body.className = 'ep-weekcalendar-body'; 151 | 152 | // 左侧分类 153 | var categoryArea = document.createElement('div'), 154 | cateHeader = document.createElement('div'), 155 | cateTitle = document.createElement('span'), 156 | cateList = document.createElement('ul'); 157 | 158 | categoryArea.className = 'ep-weekcalendar-category-area'; 159 | cateHeader.className = 'ep-weekcalendar-category-header'; 160 | cateTitle.className = 'ep-weekcalendar-category-title'; 161 | cateList.className = 'ep-weekcalendar-category-list'; 162 | cateHeader.appendChild(cateTitle); 163 | categoryArea.appendChild(cateHeader); 164 | categoryArea.appendChild(cateList); 165 | 166 | // 右侧区域 167 | var timeArea = document.createElement('div'); 168 | timeArea.className = 'ep-weekcalendar-time-area'; 169 | 170 | // 头部星期 171 | var weeks = document.createElement('div'); 172 | weeks.className = 'ep-weekcalendar-weeks'; 173 | 174 | // 时间部分中分两块 一个画日历作为背景 一个用于显示内容 175 | var main = document.createElement('div'), 176 | grid = document.createElement('div'), 177 | content = document.createElement('div'); 178 | main.className = 'ep-weekcalendar-main'; 179 | grid.className = 'ep-weekcalendar-grid'; 180 | content.className = 'ep-weekcalendar-content'; 181 | 182 | timeArea.appendChild(weeks); 183 | main.appendChild(grid); 184 | main.appendChild(content); 185 | timeArea.appendChild(main); 186 | body.appendChild(categoryArea); 187 | body.appendChild(timeArea); 188 | 189 | el.appendChild(body); 190 | 191 | this._categoryTitleEl = cateTitle; 192 | this._categoryListEl = cateList; 193 | this._weeksEl = weeks; 194 | this._gridEl = grid; 195 | this._contentEl = content; 196 | this.container.appendChild(this.el); 197 | }, 198 | 199 | // 左侧分类渲染 200 | _renderCatagories: function () { 201 | this.setCategoryTitle(); 202 | 203 | this._categoryListEl.innerHTML = ''; 204 | 205 | var i = 0, 206 | data = this._categoryData, 207 | node = document.createElement('li'), 208 | cataEl; 209 | node.className = 'ep-weekcalendar-category'; 210 | 211 | // 用行作为下标记录当前分类id集合 212 | this._categoryIndexs = []; 213 | // id为键记录索引 214 | this._categoryReocrds = {}; 215 | 216 | while (i < data.length) { 217 | this._categoryIndexs.push(data[i].id); 218 | this._categoryReocrds[data[i].id] = i; 219 | cataEl = node.cloneNode(true); 220 | this._rendercategory(data[i], cataEl); 221 | i++; 222 | } 223 | 224 | // 分类重绘必定重绘网格和内容 225 | this._renderGrid(); 226 | this._rednerContent(); 227 | 228 | }, 229 | 230 | _rendercategory: function (cate, cateEl) { 231 | cateEl.setAttribute('data-cateid', cate.id); 232 | 233 | var titleEl = document.createElement('span'), 234 | contentEl = document.createElement('span'); 235 | titleEl.className = 'title'; 236 | contentEl.className = 'content'; 237 | 238 | titleEl.innerHTML = cate.name; 239 | contentEl.innerHTML = cate.content; 240 | cateEl.appendChild(titleEl); 241 | cateEl.appendChild(contentEl); 242 | 243 | this.fire('categoryRender', { 244 | categoryEl: cateEl, 245 | titleEl: titleEl, 246 | contentEl: contentEl 247 | }); 248 | 249 | this._categoryListEl.appendChild(cateEl); 250 | 251 | this.fire('agterCategoryRender', { 252 | categoryEl: cateEl, 253 | titleEl: titleEl, 254 | contentEl: contentEl 255 | }); 256 | }, 257 | 258 | // 右侧网格 259 | _renderGrid: function () { 260 | this._gridEl.innerHTML = ''; 261 | 262 | var rowNode = document.createElement('div'), 263 | itemNode = document.createElement('span'), 264 | rowsNum = this._categoryData.length, 265 | i = 0, 266 | j = 0, 267 | row, item; 268 | 269 | rowNode.className = 'ep-weekcalendar-grid-row'; 270 | itemNode.className = 'ep-weekcalendar-grid-item'; 271 | 272 | while (i < rowsNum) { 273 | row = rowNode.cloneNode(); 274 | row.setAttribute('data-i', i); 275 | j = 0; 276 | 277 | while (j < 7) { 278 | item = itemNode.cloneNode(); 279 | // 周末标识 280 | if (this.dayStartFromSunday) { 281 | if (j === 0 || j === 6) { 282 | item.className += ' weekend'; 283 | } 284 | } else { 285 | if (j > 4) { 286 | item.className += ' weekend'; 287 | } 288 | } 289 | 290 | item.setAttribute('data-i', i); 291 | item.setAttribute('data-j', j); 292 | row.appendChild(item); 293 | 294 | j++; 295 | } 296 | 297 | this._gridEl.appendChild(row); 298 | 299 | i++; 300 | } 301 | 302 | rowNode = itemNode = row = item = null; 303 | }, 304 | 305 | // 右侧内容 306 | _rednerContent: function () { 307 | this._contentEl.innerHTML = ''; 308 | 309 | var i = 0, 310 | node = document.createElement('div'), 311 | row; 312 | 313 | node.className = 'ep-weekcalendar-content-row'; 314 | 315 | while (i < this._categoryData.length) { 316 | row = node.cloneNode(); 317 | row.setAttribute('data-i', i); 318 | 319 | this._contentEl.appendChild(row); 320 | ++i; 321 | } 322 | 323 | row = node = null; 324 | 325 | }, 326 | 327 | // 日期切换时清空内容 328 | _clearContent: function () { 329 | var rows = this._contentEl.childNodes, 330 | i = 0; 331 | 332 | while (i < rows.length) { 333 | rows[i].innerHTML && (rows[i].innerHTML = ''); 334 | ++i; 335 | } 336 | 337 | // 部件数据清空 338 | this._widgetData = {}; 339 | }, 340 | 341 | // 渲染日历星期头部 342 | _renderWeeks: function () { 343 | this._weeksEl.innerHTML = ''; 344 | var i = 0, 345 | currDate = this._startDate.clone(), 346 | node = document.createElement('div'), 347 | week; 348 | node.className = 'ep-weekcalendar-week'; 349 | 350 | // 单元格列作为下标记录日期 351 | this._dateRecords = []; 352 | 353 | while (i++ < 7) { 354 | // 更新记录日期 355 | this._dateRecords.push(currDate.clone()); 356 | 357 | week = node.cloneNode(true); 358 | this._renderWeek(currDate, week); 359 | currDate.add(1, 'day'); 360 | } 361 | 362 | // 切换日期 需要重绘内容区域 363 | this._rednerContent(); 364 | }, 365 | 366 | _renderWeek: function (date, node) { 367 | var dateText = date.format('YYYY-MM-DD'), 368 | day = date.isoWeekday(); 369 | // dateText = '周' + this._WEEKSNAME[date.isoWeekday() - 1] + ' ' + date.format('MM-DD'); 370 | // node.innerText = '周' + this._WEEKSNAME[day-1] + ' ' + date.format('MM-DD'); 371 | 372 | if (day > 5) { 373 | node.className += ' weekend'; 374 | } 375 | if (date.isSame(this.today, 'day')) { 376 | node.className += ' today'; 377 | } 378 | 379 | node.setAttribute('data-date', dateText); 380 | node.setAttribute('date-isoweekday', day); 381 | 382 | var ev = this.fire('dateRender', { 383 | // 当前完整日期 384 | date: dateText, 385 | // iso星期 386 | isoWeekday: day, 387 | // 显示的文本 388 | dateText: '周' + this._WEEKSNAME[day - 1] + ' ' + date.format('MM-DD'), 389 | // classname 390 | dateCls: node.className, 391 | // 日历el 392 | el: this.el, 393 | // 当前el 394 | dateEl: node 395 | }); 396 | 397 | // 处理事件的修改 398 | node.innerHTML = ev.dateText; 399 | node.className = ev.dateCls; 400 | 401 | this._weeksEl.appendChild(node); 402 | 403 | this.fire('afterDateRender', { 404 | // 当前完整日期 405 | date: dateText, 406 | // iso星期 407 | isoWeekday: day, 408 | // 显示的文本 409 | dateText: node.innerHTML, 410 | // classname 411 | dateCls: node.className, 412 | // 日历el 413 | el: this.el, 414 | // 当前el 415 | dateEl: node 416 | }); 417 | }, 418 | 419 | // 绘制可变部分 420 | _renderChanged: function () { 421 | // 清空内容 422 | this._clearContent(); 423 | // 初始日期范围 424 | // 渲染标题和日期 425 | this._initStartEnd(); 426 | 427 | var start = this._startDate.format('YYYY-MM-DD'), 428 | end = this._endDate.format('YYYY-MM-DD'); 429 | 430 | this._renderCalendarTitle(); 431 | this._renderWeeks(); 432 | 433 | // 渲染完成 434 | this.fire('dateChanged', { 435 | startDate: start, 436 | endDate: end, 437 | categorys: this._categoryIndexs 438 | }); 439 | 440 | }, 441 | 442 | // 绘制头部标题 443 | _renderCalendarTitle: function () { 444 | var start_text = this._startDate.format('YYYY年MM月DD日'), 445 | end_text = 446 | this._endDate.format(this._startDate.isSame(this._endDate, 'month') ? 'DD日' : 'MM月-DD日'); 447 | 448 | // 触发标题改变事件 449 | var ev = this.fire('titleChanged', { 450 | startDate: this._startDate.format('YYYY-MM-DD'), 451 | endDate: this._endDate.format('YYYY-MM-DD'), 452 | title: start_text + ' - ' + end_text, 453 | titleEl: this._titleEl 454 | }); 455 | 456 | this._titleEl.innerHTML = ev.title; 457 | }, 458 | 459 | // 星期文本 460 | _WEEKSNAME: ['一', '二', '三', '四', '五', '六', '日'], 461 | 462 | // 初始一周的开始和结束 463 | _initStartEnd: function () { 464 | var startEnd = this._getStartEnd(); 465 | 466 | this._startDate = startEnd.start; 467 | this._endDate = startEnd.end; 468 | }, 469 | 470 | _getStartEnd: function (date) { 471 | date = moment(date || this.date, 'YYYY-MM-DD'); 472 | 473 | var isoWeekday = date.isoWeekday(), 474 | start, end; 475 | 476 | if (this.dayStartFromSunday) { 477 | start = date.clone().subtract(isoWeekday === 7 ? 0 : 478 | isoWeekday, 'day'); 479 | } else { 480 | start = date.clone().subtract(isoWeekday - 1, 'day'); 481 | } 482 | end = start.clone().add(6, 'day'); 483 | return { 484 | start: start, 485 | end: end 486 | }; 487 | }, 488 | 489 | // 根据指定日期获取周视图周中的开始和结束日期 490 | getStartEnd: function (date) { 491 | var o = this._getStartEnd(date); 492 | return { 493 | start: o.start.format('YYYY-MM-DD'), 494 | end: o.end.format('YYYY-MM-DD') 495 | }; 496 | }, 497 | 498 | // 初始化事件 499 | _initEvent: function () { 500 | var me = this; 501 | 502 | // 点击的行索引 503 | var row, 504 | // 开始列索引 505 | columnStart, 506 | // 结束列索引 507 | columnEnd, 508 | // 是否在按下、移动、松开的click中 509 | isDurringClick = false, 510 | // 是否移动过 用于处理按下没有移动直接松开的过程 511 | isMoveing = false, 512 | $columns, 513 | // 网格左侧宽度 514 | gridLeft, 515 | // 每列的宽度 516 | columnWidth; 517 | jQuery(this.el) 518 | // 按下鼠标 记录分类和开始列 519 | .on('mousedown.weekcalendar', '.ep-weekcalendar-content-row', function (e) { 520 | isDurringClick = true; 521 | gridLeft = jQuery(me._gridEl).offset().left; 522 | columnWidth = jQuery(me._gridEl).width() / 7; 523 | jQuery(me._gridEl).find('.ep-weekcalendar-grid-item').removeClass(me._selectedCls); 524 | 525 | row = this.getAttribute('data-i'); 526 | $columns = jQuery(me._gridEl).find('.ep-weekcalendar-grid-row').eq(row).children(); 527 | 528 | columnStart = (e.pageX - gridLeft) / columnWidth >> 0; 529 | 530 | }) 531 | // 前后一周 532 | .on('click.weekcalendar', '.ep-weekcalendar-header-btn', function (e) { 533 | var $this = jQuery(this), 534 | isPrev = $this.hasClass('ep-weekcalendar-header-btn-prev'), 535 | isNext = $this.hasClass('ep-weekcalendar-header-btn-next'), 536 | ev, 537 | start = me._startDate.format('YYYY-MM-DD'), 538 | end = me._endDate.format('YYYY-MM-DD'); 539 | 540 | if (isPrev) { 541 | ev = me.fire('prevWeekClick', { 542 | ev: e, 543 | start: start, 544 | end: end 545 | }); 546 | } else if (isNext) { 547 | ev = me.fire('nextWeekClick', { 548 | ev: e, 549 | start: start, 550 | end: end 551 | }); 552 | } 553 | 554 | if (ev && !ev.cancel) { 555 | me[(isPrev ? 'prev' : 'next') + 'Week'](); 556 | } 557 | 558 | }) 559 | // 小部件点击 由于之前的点击用了down move up来做 这里不能直接使用click 因为click触发太晚 560 | .on('mousedown.weekcalendar', '.ep-weekcalendar-content-widget', function (e) { 561 | e.stopPropagation(); 562 | }) 563 | .on('mouseup.weekcalendar', '.ep-weekcalendar-content-widget', function (e) { 564 | e.stopPropagation(); 565 | 566 | me.fire('widgetClick', { 567 | ev: e, 568 | categoryId: me._categoryIndexs[this.parentNode.getAttribute('data-i')], 569 | widgetId: this.getAttribute('data-id'), 570 | start: this.getAttribute('data-start'), 571 | end: this.getAttribute('data-end'), 572 | title: this.title 573 | }); 574 | }) 575 | // 头部日期点击 576 | .on('click.weekcalendar', '.ep-weekcalendar-week', function (e) { 577 | me.fire('dateClick', { 578 | ev: e, 579 | date: this.getAttribute('data-date'), 580 | isoWeekday: this.getAttribute('data-isoweekday'), 581 | dateEl: this 582 | }); 583 | }) 584 | // 分类点击 585 | .on('click.weekcalendar', '.ep-weekcalendar-category', function (e) { 586 | var id = this.getAttribute('data-cateid'); 587 | me.fire('categoryClick', { 588 | ev: e, 589 | categoryId: id, 590 | title: this.firstChild.innerHTML, 591 | categoryIndex: me._categoryReocrds[id] 592 | }); 593 | }); 594 | 595 | // 移动和松开 松开鼠标 记录结束列 触发点击事件 596 | // 必须绑定在body上 否则直接移除后 不能正确结束move事件 597 | jQuery('body') 598 | // 点击移动过程中 实时响应选中状态 599 | .on('mousemove.weekcalendar', function (e) { 600 | if (!isDurringClick) { 601 | return; 602 | } 603 | isMoveing = true; 604 | 605 | // 当前列索引 606 | var currColumn; 607 | 608 | currColumn = (e.pageX - gridLeft) / columnWidth >> 0; 609 | 610 | // 修正溢出 611 | currColumn = currColumn > 6 ? 6 : currColumn; 612 | currColumn = currColumn < 0 ? 0 : currColumn; 613 | 614 | $columns.removeClass(me._selectedCls); 615 | 616 | // 起止依次选中 617 | var start = Math.min(columnStart, currColumn), 618 | end = Math.max(columnStart, currColumn); 619 | 620 | do { 621 | $columns.eq(start).addClass(me._selectedCls); 622 | } while (++start <= end); 623 | 624 | }) 625 | // 鼠标松开 626 | .on('mouseup.weekcalendar', function (e) { 627 | if (!isDurringClick) { 628 | return; 629 | } 630 | 631 | var startIndex = -1, 632 | endIndex = -1; 633 | 634 | columnEnd = (e.pageX - gridLeft) / columnWidth >> 0; 635 | 636 | columnEnd = columnEnd > 6 ? 6 : columnEnd; 637 | 638 | // 没有移动过时 639 | if (!isMoveing) { 640 | startIndex = endIndex = columnEnd; 641 | // 直接down up 没有move的过程则只会有一个选中的,直接以结束的作为处理即可 642 | $columns.eq(columnEnd).addClass(me._selectedCls) 643 | .siblings().removeClass(me._selectedCls); 644 | } else { 645 | startIndex = Math.min(columnStart, columnEnd); 646 | endIndex = Math.max(columnStart, columnEnd); 647 | } 648 | 649 | // 触发点击事件 650 | me.fire('cellClick', { 651 | // 分类id 652 | categoryId: me._categoryIndexs[row], 653 | // 时间1 654 | startDate: me._dateRecords[startIndex].format('YYYY-MM-DD'), 655 | // 日期2 656 | endDate: me._dateRecords[endIndex].format('YYYY-MM-DD'), 657 | // 行索引 658 | rowIndex: row, 659 | // 列范围 660 | columnIndexs: (function (i, j) { 661 | var arr = []; 662 | while (i <= j) { 663 | arr.push(i++); 664 | } 665 | return arr; 666 | }(startIndex, endIndex)) 667 | }); 668 | 669 | row = columnStart = columnEnd = isMoveing = isDurringClick = false; 670 | }); 671 | }, 672 | 673 | // 左侧分类标题 674 | setCategoryTitle: function (title) { 675 | title && (this._categoryTitle = title); 676 | this._categoryTitleEl.innerHTML = this._categoryTitle; 677 | }, 678 | getCategoryTitle: function () { 679 | return this._categoryTitle; 680 | }, 681 | 682 | // 左侧分类数据 683 | setCategory: function (data) { 684 | if (!(data instanceof Array)) { 685 | this.throwError('分类数据必须是一个数组'); 686 | return; 687 | } 688 | this._categoryData = data; 689 | 690 | this._renderCatagories(); 691 | 692 | this._renderChanged(); 693 | }, 694 | getCategory: function () { 695 | return this._categoryData; 696 | }, 697 | 698 | // 根据行列获取日历网格 699 | getGridElByPos: function (i, j) { 700 | var $row = jQuery('.ep-weekcalendar-grid-row[data-i="' + i + '"]', this._gridEl); 701 | 702 | return $row.length ? $row.find('[data-j="' + j + '"]')[0] : null; 703 | }, 704 | 705 | // 指定一个日期,渲染到能能显示当前日期的视图 706 | setDate: function (dateText) { 707 | if (/^\d{1,2}-\d{1,2}$/.test(dateText)) { 708 | dateText = (new Date()).getFullYear() + '-' + dateText; 709 | } 710 | var date = moment(dateText, 'YYYY-MM-DD'); 711 | 712 | if (!date.isValid()) { 713 | this.throwError('指定日期不合法'); 714 | return false; 715 | } 716 | 717 | var sn = this._getStartEnd(date); 718 | 719 | if (sn.start.isSame(this._startDate, 'day')) { 720 | // 就是当前视图 721 | return false; 722 | } 723 | 724 | this._setDate(date); 725 | }, 726 | 727 | _setDate: function (date) { 728 | this.date = date.format('YYYY-MM-DD'); 729 | 730 | // 调整日期和标题 731 | this._renderChanged(); 732 | }, 733 | 734 | // 上一周 735 | prevWeek: function () { 736 | this._setDate(this._startDate.clone().subtract(1, 'day')); 737 | }, 738 | 739 | // 下一周 740 | nextWeek: function () { 741 | this._setDate(this._endDate.clone().add(1, 'day')); 742 | }, 743 | 744 | // 回今天视图 745 | goToday: function () { 746 | var today = moment(); 747 | if (today.isBetween(this._startDate, this._endDate)) { 748 | // 今天在 当前视图中 则不用处理 749 | return false; 750 | } 751 | 752 | this.today = today.format('YYYY-MM-DD'); 753 | this._setDate(today); 754 | }, 755 | 756 | // 计算指定日期的分钟或秒数 757 | _getNumByUnits: function (dateStr) { 758 | var temp = dateStr.split(this._dateTimeSplit), 759 | date = temp[0]; 760 | 761 | // 处理左侧溢出 762 | if (this._startDate.isAfter(date, 'day')) { 763 | // 指定日期在开始日期之前 764 | return 0; 765 | } 766 | // 右侧溢出直接算作7天即可 767 | 768 | var times = (temp[1] || '').split(':'), 769 | days = (function (startDate) { 770 | var currDate = startDate.clone(), 771 | i = 0, 772 | d = moment(date, 'YYYY-MM-DD'); 773 | while (i < 7) { 774 | if (currDate.isSame(d, 'day')) { 775 | return i; 776 | } else { 777 | currDate.add(1, 'day'); 778 | ++i; 779 | } 780 | } 781 | 782 | console && console.error && console.error('计算天数时出错!'); 783 | return i; 784 | }(this._startDate)), 785 | hours = parseInt(times[0], 10) || 0, 786 | minutes = parseInt(times[1], 10) || 0, 787 | seconds = parseInt(times[2], 10) || 0, 788 | // 对应分钟数 789 | result = days * this._DAYMINUTES + hours * 60 + minutes; 790 | 791 | return this.posUnit == 'minute' ? result : (result * 60 + seconds); 792 | }, 793 | 794 | // 计算日期时间的百分比位置 795 | _getPos: function (dateStr) { 796 | var p = this._getNumByUnits(dateStr) / (this.posUnit == 'minute' ? this._WEEKMINUTES : this._WEEKSECONDS); 797 | 798 | return p > 1 ? 1 : p; 799 | }, 800 | /** 801 | * 检查是否发生重叠 802 | * 803 | * @param {Object} data 当前要加入的数据 804 | * @returns false 或 和当前部件重叠的元素数组 805 | */ 806 | _checkOccupied: function (data) { 807 | 808 | if (!this._widgetData[data.categoryId]) { 809 | return false; 810 | } 811 | 812 | var i = 0, 813 | cate = this._widgetData[data.categoryId], 814 | len = cate.length, 815 | result = false, 816 | occupied = []; 817 | 818 | for (; i < len; ++i) { 819 | if (data.start < cate[i].end && data.end > cate[i].start) { 820 | occupied.push(cate[i]); 821 | 822 | result = true; 823 | } 824 | } 825 | 826 | return result ? occupied : false; 827 | }, 828 | // 缓存widget数据 829 | _cacheWidgetData: function (data) { 830 | if (!this._widgetData[data.categoryId]) { 831 | this._widgetData[data.categoryId] = []; 832 | } 833 | // 记录当前的 834 | this._widgetData[data.categoryId].push(data); 835 | }, 836 | // 新增一个小部件 837 | addWidget: function (data) { 838 | var row = this._contentEl.childNodes[this._categoryReocrds[data.categoryId]]; 839 | 840 | if (!row) { 841 | this.throwError('对应分类不存在,添加失败'); 842 | return false; 843 | } 844 | 845 | // 先查找是否含有 846 | var $aim = jQuery('.ep-weekcalendar-content-widget[data-id="' + data.id + '"]', row); 847 | 848 | if ($aim.length) { 849 | // 已经存在则不添加 850 | return $aim[0]; 851 | } 852 | 853 | // 创建部件 854 | var widget = document.createElement('div'), 855 | title = document.createElement('span'), 856 | content = document.createElement('p'), 857 | startPos = this._getPos(data.start), 858 | endPos = this._getPos(data.end), 859 | _data = { 860 | categoryId: data.categoryId, 861 | id: data.id, 862 | start: startPos, 863 | end: endPos, 864 | el: widget, 865 | data: data 866 | }; 867 | 868 | widget.className = 'ep-weekcalendar-content-widget'; 869 | title.className = 'ep-weekcalendar-content-widget-title'; 870 | content.className = 'ep-weekcalendar-content-widget-content'; 871 | 872 | widget.appendChild(title); 873 | widget.appendChild(content); 874 | 875 | widget.style.left = startPos * 100 + '%'; 876 | widget.style.right = (1 - endPos) * 100 + '%'; 877 | data.bgColor && (widget.style.backgroundColor = data.bgColor); 878 | 879 | data.id && widget.setAttribute('data-id', data.id); 880 | widget.setAttribute('data-start', data.start); 881 | widget.setAttribute('data-end', data.end); 882 | 883 | title.innerHTML = data.title; 884 | data.content && (content.innerHTML = data.content); 885 | widget.title = data.title + ' 从' + moment(data.start, 'YYYY-MM-DD' + this._dateTimeSplit + 'HH:mm:ss').format('M月D日 HH:mm') + ' 到 ' + moment(data.end, 'YYYY-MM-DD' + this._dateTimeSplit + 'HH:mm:ss').format('M月D日 HH:mm'); 886 | 887 | // 检查是否发生重叠 888 | var isoccupied = this._checkOccupied(_data); 889 | 890 | if (isoccupied) { 891 | // 触发重叠事件 892 | var occupiedEv = this.fire('widgetoccupied', { 893 | occupiedWidgets: (function () { 894 | var arr = []; 895 | for (var i = 0, l = isoccupied.length; i < l; ++i) { 896 | arr.push(isoccupied[i].el); 897 | } 898 | return arr; 899 | })(), 900 | currWidget: widget, 901 | widgetData: data 902 | }); 903 | 904 | // 取消后续执行 905 | if (occupiedEv.cancel) { 906 | return false; 907 | } 908 | } 909 | 910 | // 缓存数据 911 | this._cacheWidgetData(_data); 912 | 913 | var addEv = this.fire('widgetAdd', { 914 | widgetId: data.id, 915 | categoryId: data.categoryId, 916 | start: data.start, 917 | end: data.end, 918 | startPos: startPos, 919 | endPos: endPos, 920 | widgetEl: widget 921 | }); 922 | 923 | if (addEv.cancel) { 924 | return false; 925 | } 926 | 927 | row.appendChild(widget); 928 | 929 | this.fire('afterWidgetAdd', { 930 | widgetId: data.id, 931 | categoryId: data.categoryId, 932 | start: data.start, 933 | end: data.end, 934 | startPos: startPos, 935 | endPos: endPos, 936 | widgetEl: widget 937 | }); 938 | 939 | return widget; 940 | }, 941 | 942 | // 移除一个小部件 943 | removeWidget: function (id) { 944 | var $widget = jQuery('.ep-weekcalendar-content-widget[data-id="' + id + '"]', this._contentEl); 945 | 946 | if (!$widget.length) { 947 | return false; 948 | } 949 | 950 | var categoryId = this._categoryIndexs[$widget.parent().data('i')], 951 | widgetData = this._widgetData[categoryId]; 952 | 953 | // 移除widget数据 954 | jQuery.each(widgetData, function (i, item) { 955 | if (item.id == id) { 956 | widgetData.splice(i, 1); 957 | return false; 958 | } 959 | }); 960 | 961 | return $widget.remove(); 962 | }, 963 | 964 | // 此方法在js等资源加载完成后自动调用 965 | create: function () { 966 | 967 | // 触发创建前事件 968 | this.fire('beforeCreate'); 969 | 970 | this._initCfg(); 971 | this.render(); 972 | 973 | this._initEvent(); 974 | 975 | this.fire('afterCreate'); 976 | } 977 | }); 978 | -------------------------------------------------------------------------------- /component/weekcalendar/weekcalendar.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // 边框颜色 4 | $border_color: #ddd; 5 | // 周末背景 6 | $weekend_bg: #f2f2f2; 7 | // 选中背景 8 | $selected_bg: #eaf8fa; 9 | 10 | .ep-weekcalendar { 11 | position: relative; 12 | 13 | width: 100%; 14 | height: 100%; 15 | } 16 | 17 | 18 | /* 头部 */ 19 | 20 | .ep-weekcalendar-header { 21 | position: relative; 22 | 23 | height: 42px; 24 | margin-bottom: 10px; 25 | padding: 8px 16px 0; 26 | 27 | text-align: center; 28 | 29 | background-color: #e0f2ff; 30 | 31 | &:after { 32 | display: table; 33 | clear: both; 34 | 35 | content: ""; 36 | } 37 | 38 | @at-root { 39 | .ep-weekcalendar-header-left { 40 | float: left; 41 | 42 | height: 100%; 43 | } 44 | .ep-weekcalendar-header-right { 45 | float: right; 46 | 47 | height: 100%; 48 | } 49 | 50 | .ep-weekcalendar-header-center { 51 | font-size: 14px; 52 | line-height: 30px; 53 | 54 | position: absolute; 55 | left: 50%; 56 | 57 | width: 300px; 58 | height: 30px; 59 | margin-left: -150px; 60 | } 61 | 62 | .ep-weekcalendar-header-btn { 63 | display: block; 64 | 65 | width: 30px; 66 | height: 30px; 67 | 68 | cursor: pointer; 69 | 70 | background-repeat: no-repeat; 71 | background-position: center center; 72 | } 73 | .ep-weekcalendar-header-btn-prev { 74 | float: left; 75 | 76 | background-image: url("./images/btn-prev.png"); 77 | } 78 | .ep-weekcalendar-header-btn-next { 79 | float: right; 80 | 81 | background-image: url("./images/btn-next.png"); 82 | } 83 | .ep-weekcalendar-title { 84 | display: inline-block; 85 | 86 | width: 230px; 87 | height: 100%; 88 | } 89 | } 90 | } 91 | 92 | 93 | /* body */ 94 | 95 | .ep-weekcalendar-body { 96 | position: absolute; 97 | top: 60px; 98 | right: 0; 99 | bottom: 0; 100 | left: 0; 101 | } 102 | 103 | 104 | /* 左侧分类 */ 105 | 106 | .ep-weekcalendar-category-area { 107 | position: absolute; 108 | top: 0; 109 | 110 | width: 200px; 111 | } 112 | 113 | .ep-weekcalendar-category-header { 114 | height: 36px; 115 | margin-right: 10px; 116 | padding-right: 18px; 117 | 118 | text-align: center; 119 | 120 | color: #fff; 121 | -webkit-border-radius: 0 18px 18px 0; 122 | -moz-border-radius: 0 18px 18px 0; 123 | border-radius: 0 18px 18px 0; 124 | background: #51a6ef; 125 | } 126 | 127 | .ep-weekcalendar-category-title { 128 | font-size: 14px; 129 | line-height: 36px; 130 | } 131 | 132 | .ep-weekcalendar-category-list { 133 | list-style: none; 134 | margin: 0; 135 | padding: 0; 136 | } 137 | 138 | .ep-weekcalendar-category { 139 | box-sizing: border-box; 140 | height: 50px; 141 | .title, 142 | .content { 143 | display: block; 144 | 145 | box-sizing: border-box; 146 | height: 50%; 147 | padding-left: 20px; 148 | 149 | color: #333; 150 | } 151 | .content { 152 | color: #888; 153 | } 154 | } 155 | 156 | 157 | /* 日历中间 */ 158 | 159 | .ep-weekcalendar-time-area { 160 | min-height: 100px; 161 | margin-left: 200px; 162 | 163 | background: #fdfdfd; 164 | } 165 | 166 | 167 | /* 头部星期 */ 168 | 169 | .ep-weekcalendar-weeks { 170 | line-height: 36px; 171 | 172 | -webkit-box-sizing: border-box; 173 | -moz-box-sizing: border-box; 174 | box-sizing: border-box; 175 | height: 36px; 176 | 177 | text-align: center; 178 | } 179 | 180 | .ep-weekcalendar-weeks:after { 181 | display: table; 182 | clear: both; 183 | 184 | content: ""; 185 | } 186 | 187 | .ep-weekcalendar-week { 188 | float: left; 189 | overflow: hidden; 190 | 191 | -webkit-box-sizing: border-box; 192 | -moz-box-sizing: border-box; 193 | box-sizing: border-box; 194 | width: 14.2857%; 195 | 196 | white-space: nowrap; 197 | text-overflow: ellipsis; 198 | 199 | -o-text-overflow: ellipsis; 200 | 201 | &.weekend { 202 | background: $weekend_bg; 203 | } 204 | } 205 | 206 | .ep-weekcalendar-main { 207 | position: relative; 208 | } 209 | /* 网格 */ 210 | 211 | .ep-weekcalendar-grid { 212 | box-sizing: border-box; 213 | margin-top: -1px; 214 | 215 | @at-root { 216 | .ep-weekcalendar-grid-row { 217 | height: 50px; 218 | &:after { 219 | display: table; 220 | clear: both; 221 | 222 | content: ""; 223 | } 224 | } 225 | .ep-weekcalendar-grid-item { 226 | display: block; 227 | float: left; 228 | 229 | box-sizing: border-box; 230 | width: 14.2857%; 231 | height: 100%; 232 | &.weekend { 233 | background: $weekend_bg; 234 | } 235 | &.selected { 236 | background: $selected_bg; 237 | } 238 | } 239 | } 240 | } 241 | /* content */ 242 | .ep-weekcalendar-content { 243 | line-height: 24px; 244 | 245 | position: absolute; 246 | z-index: 1; 247 | top: 0; 248 | right: 0; 249 | bottom: 0; 250 | left: 0; 251 | 252 | @at-root { 253 | .ep-weekcalendar-content-row { 254 | position: relative; 255 | 256 | box-sizing: border-box; 257 | height: 50px; 258 | 259 | overflow: hidden; 260 | } 261 | .ep-weekcalendar-content-widget { 262 | position: absolute; 263 | 264 | box-sizing: border-box; 265 | height: 100%; 266 | padding: 0 5px; 267 | 268 | color: #fff; 269 | background-color: #6bb2ee; 270 | } 271 | } 272 | } 273 | // 边框处理 274 | .ep-weekcalendar.border { 275 | // 左侧列表 276 | .ep-weekcalendar-category-list { 277 | border-left: 1px solid $border_color; // border-bottom:1px solid $border_color; 278 | } 279 | .ep-weekcalendar-category { 280 | border-bottom: 1px solid $border_color; 281 | } // 星期 282 | .ep-weekcalendar-weeks { 283 | line-height: 34px; 284 | 285 | border: 1px solid $border_color; 286 | border-right: none; 287 | } 288 | .ep-weekcalendar-week { 289 | border-right: 1px solid $border_color; 290 | } 291 | 292 | /* 日历网格 */ 293 | .ep-weekcalendar-grid { 294 | border-right: 1px solid $border_color; 295 | border-bottom: 1px solid $border_color; 296 | } 297 | .ep-weekcalendar-grid-item { 298 | border-top: 1px solid $border_color; 299 | border-left: 1px solid $border_color; 300 | } 301 | 302 | /* 内容行也需要一个透明边框 防止被内容盖住边框*/ 303 | .ep-weekcalendar-content-row { 304 | border: 1px solid transparent; 305 | border-bottom: none; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /lib/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.10.6 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | !function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Hc.apply(null,arguments)}function b(a){Hc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Jc)d=Jc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Kc===!1&&(Kc=!0,a.updateOffset(this),Kc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){return 0>a?Math.ceil(a):Math.floor(a)}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=p(b)),c}function r(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function s(){}function t(a){return a?a.toLowerCase().replace("_","-"):a}function u(a){for(var b,c,d,e,f=0;f0;){if(d=v(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&r(e,c,!0)>=b-1)break;b--}f++}return null}function v(a){var b=null;if(!Lc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ic._abbr,require("./locale/"+a),w(b)}catch(c){}return Lc[a]}function w(a,b){var c;return a&&(c="undefined"==typeof b?y(a):x(a,b),c&&(Ic=c)),Ic._abbr}function x(a,b){return null!==b?(b.abbr=a,Lc[a]=Lc[a]||new s,Lc[a].set(b),w(a),Lc[a]):(delete Lc[a],null)}function y(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ic;if(!c(a)){if(b=v(a))return b;a=[a]}return u(a)}function z(a,b){var c=a.toLowerCase();Mc[c]=Mc[c+"s"]=Mc[b]=a}function A(a){return"string"==typeof a?Mc[a]||Mc[a.toLowerCase()]:void 0}function B(a){var b,c,d={};for(c in a)f(a,c)&&(b=A(c),b&&(d[b]=a[c]));return d}function C(b,c){return function(d){return null!=d?(E(this,b,d),a.updateOffset(this,c),this):D(this,b)}}function D(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function E(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function F(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=A(a),"function"==typeof this[a])return this[a](b);return this}function G(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function H(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Qc[a]=e),b&&(Qc[b[0]]=function(){return G(e.apply(this,arguments),b[1],b[2])}),c&&(Qc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function I(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function J(a){var b,c,d=a.match(Nc);for(b=0,c=d.length;c>b;b++)Qc[d[b]]?d[b]=Qc[d[b]]:d[b]=I(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function K(a,b){return a.isValid()?(b=L(b,a.localeData()),Pc[b]=Pc[b]||J(b),Pc[b](a)):a.localeData().invalidDate()}function L(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Oc.lastIndex=0;d>=0&&Oc.test(a);)a=a.replace(Oc,c),Oc.lastIndex=0,d-=1;return a}function M(a){return"function"==typeof a&&"[object Function]"===Object.prototype.toString.call(a)}function N(a,b,c){dd[a]=M(b)?b:function(a){return a&&c?c:b}}function O(a,b){return f(dd,a)?dd[a](b._strict,b._locale):new RegExp(P(a))}function P(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=q(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function X(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),T(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function Y(b){return null!=b?(X(this,b),a.updateOffset(this,!0),this):D(this,"Month")}function Z(){return T(this.year(),this.month())}function $(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[gd]<0||c[gd]>11?gd:c[hd]<1||c[hd]>T(c[fd],c[gd])?hd:c[id]<0||c[id]>24||24===c[id]&&(0!==c[jd]||0!==c[kd]||0!==c[ld])?id:c[jd]<0||c[jd]>59?jd:c[kd]<0||c[kd]>59?kd:c[ld]<0||c[ld]>999?ld:-1,j(a)._overflowDayOfYear&&(fd>b||b>hd)&&(b=hd),j(a).overflow=b),a}function _(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function aa(a,b){var c=!0;return g(function(){return c&&(_(a+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ba(a,b){od[a]||(_(b),od[a]=!0)}function ca(a){var b,c,d=a._i,e=pd.exec(d);if(e){for(j(a).iso=!0,b=0,c=qd.length;c>b;b++)if(qd[b][1].exec(d)){a._f=qd[b][0];break}for(b=0,c=rd.length;c>b;b++)if(rd[b][1].exec(d)){a._f+=(e[6]||" ")+rd[b][0];break}d.match(ad)&&(a._f+="Z"),va(a)}else a._isValid=!1}function da(b){var c=sd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ca(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ea(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fa(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ga(a){return ha(a)?366:365}function ha(a){return a%4===0&&a%100!==0||a%400===0}function ia(){return ha(this.year())}function ja(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Da(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ka(a){return ja(a,this._week.dow,this._week.doy).week}function la(){return this._week.dow}function ma(){return this._week.doy}function na(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function oa(a){var b=ja(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function pa(a,b,c,d,e){var f,g=6+e-d,h=fa(a,0,1+g),i=h.getUTCDay();return e>i&&(i+=7),c=null!=c?1*c:e,f=1+g+7*(b-1)-i+c,{year:f>0?a:a-1,dayOfYear:f>0?f:ga(a-1)+f}}function qa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function ra(a,b,c){return null!=a?a:null!=b?b:c}function sa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ta(a){var b,c,d,e,f=[];if(!a._d){for(d=sa(a),a._w&&null==a._a[hd]&&null==a._a[gd]&&ua(a),a._dayOfYear&&(e=ra(a._a[fd],d[fd]),a._dayOfYear>ga(e)&&(j(a)._overflowDayOfYear=!0),c=fa(e,0,a._dayOfYear),a._a[gd]=c.getUTCMonth(),a._a[hd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[id]&&0===a._a[jd]&&0===a._a[kd]&&0===a._a[ld]&&(a._nextDay=!0,a._a[id]=0),a._d=(a._useUTC?fa:ea).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[id]=24)}}function ua(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ra(b.GG,a._a[fd],ja(Da(),1,4).year),d=ra(b.W,1),e=ra(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ra(b.gg,a._a[fd],ja(Da(),f,g).year),d=ra(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=pa(c,d,e,g,f),a._a[fd]=h.year,a._dayOfYear=h.dayOfYear}function va(b){if(b._f===a.ISO_8601)return void ca(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=L(b._f,b._locale).match(Nc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Qc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),S(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[id]<=12&&b._a[id]>0&&(j(b).bigHour=void 0),b._a[id]=wa(b._locale,b._a[id],b._meridiem),ta(b),$(b)}function wa(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function xa(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function ya(a){if(!a._d){var b=B(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ta(a)}}function za(a){var b=new n($(Aa(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Aa(a){var b=a._i,e=a._f;return a._locale=a._locale||y(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),o(b)?new n($(b)):(c(e)?xa(a):e?va(a):d(b)?a._d=b:Ba(a),a))}function Ba(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?da(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ta(b)):"object"==typeof f?ya(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ca(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,za(f)}function Da(a,b,c,d){return Ca(a,b,c,d,!1)}function Ea(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Da();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+G(~~(a/60),2)+b+G(~~a%60,2)})}function Ka(a){var b=(a||"").match(ad)||[],c=b[b.length-1]||[],d=(c+"").match(xd)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?e:-e}function La(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Da(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Da(b).local()}function Ma(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Na(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ka(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ma(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?bb(this,Ya(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ma(this)}function Oa(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Pa(a){return this.utcOffset(0,a)}function Qa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ma(this),"m")),this}function Ra(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ka(this._i)),this}function Sa(a){return a=a?Da(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Ta(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ua(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var a={};if(m(a,this),a=Aa(a),a._a){var b=a._isUTC?h(a._a):Da(a._a);this._isDSTShifted=this.isValid()&&r(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Va(){return!this._isUTC}function Wa(){return this._isUTC}function Xa(){return this._isUTC&&0===this._offset}function Ya(a,b){var c,d,e,g=a,h=null;return Ia(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=yd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[hd])*c,h:q(h[id])*c,m:q(h[jd])*c,s:q(h[kd])*c,ms:q(h[ld])*c}):(h=zd.exec(a))?(c="-"===h[1]?-1:1,g={y:Za(h[2],c),M:Za(h[3],c),d:Za(h[4],c),h:Za(h[5],c),m:Za(h[6],c),s:Za(h[7],c),w:Za(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=_a(Da(g.from),Da(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ha(g),Ia(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Za(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function $a(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function _a(a,b){var c;return b=La(b,a),a.isBefore(b)?c=$a(a,b):(c=$a(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function ab(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ba(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ya(c,d),bb(this,e,a),this}}function bb(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&E(b,"Date",D(b,"Date")+g*d),h&&X(b,D(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function cb(a,b){var c=a||Da(),d=La(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(b&&b[f]||this.localeData().calendar(f,this,Da(c)))}function db(){return new n(this)}function eb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+this>+a):(c=o(a)?+a:+Da(a),c<+this.clone().startOf(b))}function fb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+a>+this):(c=o(a)?+a:+Da(a),+this.clone().endOf(b)b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function kb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function lb(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=Da([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Pb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Kb(a,this.localeData()),this.add(a-b,"d")):b}function Qb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Rb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Sb(a,b){H(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Tb(a,b){return b._meridiemParse}function Ub(a){return"p"===(a+"").toLowerCase().charAt(0)}function Vb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Wb(a,b){b[ld]=q(1e3*("0."+a))}function Xb(){return this._isUTC?"UTC":""}function Yb(){return this._isUTC?"Coordinated Universal Time":""}function Zb(a){return Da(1e3*a)}function $b(){return Da.apply(null,arguments).parseZone()}function _b(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function bc(){return this._invalidDate}function cc(a){return this._ordinal.replace("%d",a)}function dc(a){return a}function ec(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function gc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hc(a,b,c,d){var e=y(),f=h().set(d,b);return e[c](f,a)}function ic(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return hc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=hc(a,f,c,e);return g}function jc(a,b){return ic(a,b,"months",12,"month")}function kc(a,b){return ic(a,b,"monthsShort",12,"month")}function lc(a,b){return ic(a,b,"weekdays",7,"day")}function mc(a,b){return ic(a,b,"weekdaysShort",7,"day")}function nc(a,b){return ic(a,b,"weekdaysMin",7,"day")}function oc(){var a=this._data;return this._milliseconds=Wd(this._milliseconds),this._days=Wd(this._days),this._months=Wd(this._months),a.milliseconds=Wd(a.milliseconds),a.seconds=Wd(a.seconds),a.minutes=Wd(a.minutes),a.hours=Wd(a.hours),a.months=Wd(a.months),a.years=Wd(a.years),this}function pc(a,b,c,d){var e=Ya(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function qc(a,b){return pc(this,a,b,1)}function rc(a,b){return pc(this,a,b,-1)}function sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*sc(vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=p(f/1e3),i.seconds=a%60,b=p(a/60),i.minutes=b%60,c=p(b/60),i.hours=c%24,g+=p(c/24),e=p(uc(g)),h+=e,g-=sc(vc(e)),d=p(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function uc(a){return 4800*a/146097}function vc(a){return 146097*a/4800}function wc(a){var b,c,d=this._milliseconds;if(a=A(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*q(this._months/12)}function yc(a){return function(){return this.as(a)}}function zc(a){return a=A(a),this[a+"s"]()}function Ac(a){return function(){return this._data[a]}}function Bc(){return p(this.days()/7)}function Cc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Dc(a,b,c){var d=Ya(a).abs(),e=ke(d.as("s")),f=ke(d.as("m")),g=ke(d.as("h")),h=ke(d.as("d")),i=ke(d.as("M")),j=ke(d.as("y")),k=e0,k[4]=c,Cc.apply(null,k)}function Ec(a,b){return void 0===le[a]?!1:void 0===b?le[a]:(le[a]=b,!0)}function Fc(a){var b=this.localeData(),c=Dc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Gc(){var a,b,c,d=me(this._milliseconds)/1e3,e=me(this._days),f=me(this._months);a=p(d/60),b=p(a/60),d%=60,a%=60,c=p(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Hc,Ic,Jc=a.momentProperties=[],Kc=!1,Lc={},Mc={},Nc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Oc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Pc={},Qc={},Rc=/\d/,Sc=/\d\d/,Tc=/\d{3}/,Uc=/\d{4}/,Vc=/[+-]?\d{6}/,Wc=/\d\d?/,Xc=/\d{1,3}/,Yc=/\d{1,4}/,Zc=/[+-]?\d{1,6}/,$c=/\d+/,_c=/[+-]?\d+/,ad=/Z|[+-]\d\d:?\d\d/gi,bd=/[+-]?\d+(\.\d{1,3})?/,cd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,dd={},ed={},fd=0,gd=1,hd=2,id=3,jd=4,kd=5,ld=6;H("M",["MM",2],"Mo",function(){return this.month()+1}),H("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),H("MMMM",0,0,function(a){return this.localeData().months(this,a)}),z("month","M"),N("M",Wc),N("MM",Wc,Sc),N("MMM",cd),N("MMMM",cd),Q(["M","MM"],function(a,b){b[gd]=q(a)-1}),Q(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[gd]=e:j(c).invalidMonth=a});var md="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),nd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),od={};a.suppressDeprecationWarnings=!1;var pd=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,qd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],rd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],sd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=aa("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),H(0,["YY",2],0,function(){return this.year()%100}),H(0,["YYYY",4],0,"year"),H(0,["YYYYY",5],0,"year"),H(0,["YYYYYY",6,!0],0,"year"),z("year","y"),N("Y",_c),N("YY",Wc,Sc),N("YYYY",Yc,Uc),N("YYYYY",Zc,Vc),N("YYYYYY",Zc,Vc),Q(["YYYYY","YYYYYY"],fd),Q("YYYY",function(b,c){c[fd]=2===b.length?a.parseTwoDigitYear(b):q(b)}),Q("YY",function(b,c){c[fd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return q(a)+(q(a)>68?1900:2e3)};var td=C("FullYear",!1);H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),z("week","w"),z("isoWeek","W"),N("w",Wc),N("ww",Wc,Sc),N("W",Wc),N("WW",Wc,Sc),R(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=q(a)});var ud={dow:0,doy:6};H("DDD",["DDDD",3],"DDDo","dayOfYear"),z("dayOfYear","DDD"),N("DDD",Xc),N("DDDD",Tc),Q(["DDD","DDDD"],function(a,b,c){c._dayOfYear=q(a)}),a.ISO_8601=function(){};var vd=aa("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return this>a?this:a}),wd=aa("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return a>this?this:a});Ja("Z",":"),Ja("ZZ",""),N("Z",ad),N("ZZ",ad),Q(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ka(a)});var xd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var yd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,zd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ya.fn=Ha.prototype;var Ad=ab(1,"add"),Bd=ab(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Cd=aa("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Db("gggg","weekYear"),Db("ggggg","weekYear"),Db("GGGG","isoWeekYear"),Db("GGGGG","isoWeekYear"),z("weekYear","gg"),z("isoWeekYear","GG"),N("G",_c),N("g",_c),N("GG",Wc,Sc),N("gg",Wc,Sc),N("GGGG",Yc,Uc),N("gggg",Yc,Uc),N("GGGGG",Zc,Vc),N("ggggg",Zc,Vc),R(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=q(a)}),R(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),H("Q",0,0,"quarter"),z("quarter","Q"),N("Q",Rc),Q("Q",function(a,b){b[gd]=3*(q(a)-1)}),H("D",["DD",2],"Do","date"),z("date","D"),N("D",Wc),N("DD",Wc,Sc),N("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),Q(["D","DD"],hd),Q("Do",function(a,b){b[hd]=q(a.match(Wc)[0],10)});var Dd=C("Date",!0);H("d",0,"do","day"),H("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),H("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),H("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),z("day","d"),z("weekday","e"),z("isoWeekday","E"),N("d",Wc),N("e",Wc),N("E",Wc),N("dd",cd),N("ddd",cd),N("dddd",cd),R(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),R(["d","e","E"],function(a,b,c,d){b[d]=q(a)});var Ed="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Fd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Gd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");H("H",["HH",2],0,"hour"),H("h",["hh",2],0,function(){return this.hours()%12||12}),Sb("a",!0),Sb("A",!1),z("hour","h"),N("a",Tb),N("A",Tb),N("H",Wc),N("h",Wc),N("HH",Wc,Sc),N("hh",Wc,Sc),Q(["H","HH"],id),Q(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),Q(["h","hh"],function(a,b,c){b[id]=q(a),j(c).bigHour=!0});var Hd=/[ap]\.?m?\.?/i,Id=C("Hours",!0);H("m",["mm",2],0,"minute"),z("minute","m"),N("m",Wc),N("mm",Wc,Sc),Q(["m","mm"],jd);var Jd=C("Minutes",!1);H("s",["ss",2],0,"second"),z("second","s"),N("s",Wc),N("ss",Wc,Sc),Q(["s","ss"],kd);var Kd=C("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),z("millisecond","ms"),N("S",Xc,Rc),N("SS",Xc,Sc),N("SSS",Xc,Tc);var Ld;for(Ld="SSSS";Ld.length<=9;Ld+="S")N(Ld,$c);for(Ld="S";Ld.length<=9;Ld+="S")Q(Ld,Wb);var Md=C("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var Nd=n.prototype;Nd.add=Ad,Nd.calendar=cb,Nd.clone=db,Nd.diff=ib,Nd.endOf=ub,Nd.format=mb,Nd.from=nb,Nd.fromNow=ob,Nd.to=pb,Nd.toNow=qb,Nd.get=F,Nd.invalidAt=Cb,Nd.isAfter=eb,Nd.isBefore=fb,Nd.isBetween=gb,Nd.isSame=hb,Nd.isValid=Ab,Nd.lang=Cd,Nd.locale=rb,Nd.localeData=sb,Nd.max=wd,Nd.min=vd,Nd.parsingFlags=Bb,Nd.set=F,Nd.startOf=tb,Nd.subtract=Bd,Nd.toArray=yb,Nd.toObject=zb,Nd.toDate=xb,Nd.toISOString=lb,Nd.toJSON=lb,Nd.toString=kb,Nd.unix=wb,Nd.valueOf=vb,Nd.year=td,Nd.isLeapYear=ia,Nd.weekYear=Fb,Nd.isoWeekYear=Gb,Nd.quarter=Nd.quarters=Jb,Nd.month=Y,Nd.daysInMonth=Z,Nd.week=Nd.weeks=na,Nd.isoWeek=Nd.isoWeeks=oa,Nd.weeksInYear=Ib,Nd.isoWeeksInYear=Hb,Nd.date=Dd,Nd.day=Nd.days=Pb,Nd.weekday=Qb,Nd.isoWeekday=Rb,Nd.dayOfYear=qa,Nd.hour=Nd.hours=Id,Nd.minute=Nd.minutes=Jd,Nd.second=Nd.seconds=Kd, 7 | Nd.millisecond=Nd.milliseconds=Md,Nd.utcOffset=Na,Nd.utc=Pa,Nd.local=Qa,Nd.parseZone=Ra,Nd.hasAlignedHourOffset=Sa,Nd.isDST=Ta,Nd.isDSTShifted=Ua,Nd.isLocal=Va,Nd.isUtcOffset=Wa,Nd.isUtc=Xa,Nd.isUTC=Xa,Nd.zoneAbbr=Xb,Nd.zoneName=Yb,Nd.dates=aa("dates accessor is deprecated. Use date instead.",Dd),Nd.months=aa("months accessor is deprecated. Use month instead",Y),Nd.years=aa("years accessor is deprecated. Use year instead",td),Nd.zone=aa("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Oa);var Od=Nd,Pd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Qd={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Rd="Invalid date",Sd="%d",Td=/\d{1,2}/,Ud={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Vd=s.prototype;Vd._calendar=Pd,Vd.calendar=_b,Vd._longDateFormat=Qd,Vd.longDateFormat=ac,Vd._invalidDate=Rd,Vd.invalidDate=bc,Vd._ordinal=Sd,Vd.ordinal=cc,Vd._ordinalParse=Td,Vd.preparse=dc,Vd.postformat=dc,Vd._relativeTime=Ud,Vd.relativeTime=ec,Vd.pastFuture=fc,Vd.set=gc,Vd.months=U,Vd._months=md,Vd.monthsShort=V,Vd._monthsShort=nd,Vd.monthsParse=W,Vd.week=ka,Vd._week=ud,Vd.firstDayOfYear=ma,Vd.firstDayOfWeek=la,Vd.weekdays=Lb,Vd._weekdays=Ed,Vd.weekdaysMin=Nb,Vd._weekdaysMin=Gd,Vd.weekdaysShort=Mb,Vd._weekdaysShort=Fd,Vd.weekdaysParse=Ob,Vd.isPM=Ub,Vd._meridiemParse=Hd,Vd.meridiem=Vb,w("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=aa("moment.lang is deprecated. Use moment.locale instead.",w),a.langData=aa("moment.langData is deprecated. Use moment.localeData instead.",y);var Wd=Math.abs,Xd=yc("ms"),Yd=yc("s"),Zd=yc("m"),$d=yc("h"),_d=yc("d"),ae=yc("w"),be=yc("M"),ce=yc("y"),de=Ac("milliseconds"),ee=Ac("seconds"),fe=Ac("minutes"),ge=Ac("hours"),he=Ac("days"),ie=Ac("months"),je=Ac("years"),ke=Math.round,le={s:45,m:45,h:22,d:26,M:11},me=Math.abs,ne=Ha.prototype;ne.abs=oc,ne.add=qc,ne.subtract=rc,ne.as=wc,ne.asMilliseconds=Xd,ne.asSeconds=Yd,ne.asMinutes=Zd,ne.asHours=$d,ne.asDays=_d,ne.asWeeks=ae,ne.asMonths=be,ne.asYears=ce,ne.valueOf=xc,ne._bubble=tc,ne.get=zc,ne.milliseconds=de,ne.seconds=ee,ne.minutes=fe,ne.hours=ge,ne.days=he,ne.weeks=Bc,ne.months=ie,ne.years=je,ne.humanize=Fc,ne.toISOString=Gc,ne.toString=Gc,ne.toJSON=Gc,ne.locale=rb,ne.localeData=sb,ne.toIsoString=aa("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Gc),ne.lang=Cd,H("X",0,0,"unix"),H("x",0,0,"valueOf"),N("x",_c),N("X",bd),Q("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),Q("x",function(a,b,c){c._d=new Date(q(a))}),a.version="2.10.6",b(Da),a.fn=Od,a.min=Fa,a.max=Ga,a.utc=h,a.unix=Zb,a.months=jc,a.isDate=d,a.locale=w,a.invalid=l,a.duration=Ya,a.isMoment=o,a.weekdays=lc,a.parseZone=$b,a.localeData=y,a.isDuration=Ia,a.monthsShort=kc,a.weekdaysMin=nc,a.defineLocale=x,a.weekdaysShort=mc,a.normalizeUnits=A,a.relativeTimeThreshold=Ec;var oe=a;return oe}); --------------------------------------------------------------------------------