mirror of
https://github.com/tenrok/vue2-datepicker.git
synced 2026-06-23 12:20:35 +03:00
refactor: 2.0
This commit is contained in:
@@ -7,5 +7,21 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stage-3"
|
"stage-3"
|
||||||
]
|
],
|
||||||
|
"plugins": [
|
||||||
|
"transform-vue-jsx",
|
||||||
|
"jsx-v-model"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"presets": [
|
||||||
|
["env", { "target": { "node": "current" }}],
|
||||||
|
"stage-3"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"transform-vue-jsx",
|
||||||
|
"jsx-v-model"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/build/
|
||||||
|
/config/
|
||||||
|
/dist/
|
||||||
|
/*.js
|
||||||
|
/__test__/
|
||||||
|
/test/
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// https://eslint.org/docs/user-guide/configuring
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint'
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
||||||
|
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
||||||
|
'plugin:vue/essential',
|
||||||
|
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
|
||||||
|
'standard'
|
||||||
|
],
|
||||||
|
// required to lint *.vue files
|
||||||
|
plugins: [
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
// allow async-await
|
||||||
|
'generator-star-spacing': 'off',
|
||||||
|
// allow debugger during development
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'camelcase': ['off', { properties: 'never' }],
|
||||||
|
// "vue/max-attributes-per-line": [2, {
|
||||||
|
// "singleline": 1,
|
||||||
|
// "multiline": {
|
||||||
|
// "max": 1,
|
||||||
|
// "allowFirstLine": true
|
||||||
|
// }
|
||||||
|
// }],
|
||||||
|
"vue/html-indent": ["error", 2, {
|
||||||
|
"attribute": 1,
|
||||||
|
"closeBracket": 0,
|
||||||
|
"alignAttributesVertically": false,
|
||||||
|
"ignores": []
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules/
|
node_modules/
|
||||||
|
lib
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
coverage
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "node"
|
||||||
|
- "8"
|
||||||
|
script: "npm run test:push"
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
# vue2-datepicker
|
# vue2-datepicker
|
||||||
|
|
||||||
[中文版](https://github.com/mengxiong10/vue2-datepicker/blob/master/README_CN.md)
|
[中文版](https://github.com/mengxiong10/vue2-datepicker/blob/master/README.zh-CN.md)
|
||||||
|
|
||||||
> A Datepicker Component For Vue2
|
> A Datepicker Component For Vue2
|
||||||
|
|
||||||
|
<a href="https://travis-ci.org/mengxiong10/vue2-datepicker">
|
||||||
|
<img src="https://travis-ci.org/mengxiong10/vue2-datepicker.svg?branch=master" alt="build:passed">
|
||||||
|
</a>
|
||||||
|
<a href="https://coveralls.io/github/mengxiong10/vue2-datepicker">
|
||||||
|
<img src="https://coveralls.io/repos/github/mengxiong10/vue2-datepicker/badge.svg?branch=master" alt="Badge">
|
||||||
|
</a>
|
||||||
|
<a href="LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/License-MIT-yellow.svg">
|
||||||
|
</a>
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
<https://mengxiong10.github.io/vue2-datepicker/>
|
<https://mengxiong10.github.io/vue2-datepicker/demo>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -27,13 +37,19 @@ export default {
|
|||||||
return {
|
return {
|
||||||
time1: '',
|
time1: '',
|
||||||
time2: '',
|
time2: '',
|
||||||
|
time3: '',
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
text: 'Today',
|
text: 'Today',
|
||||||
start: new Date(),
|
start: new Date(),
|
||||||
end: new Date()
|
end: new Date()
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
timePickerOptions:{
|
||||||
|
start: '00:00',
|
||||||
|
step: '00:30',
|
||||||
|
end: '23:30'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,50 +58,66 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<date-picker v-model="time1" :first-day-of-week="1"></date-picker>
|
<date-picker v-model="time1" :first-day-of-week="1"></date-picker>
|
||||||
<date-picker v-model="time2" range :shortcuts="shortcuts"></date-picker>
|
<date-picker v-model="time2" type="datetime" :time-picker-options="timePickerOptions"></date-picker>
|
||||||
|
<date-picker v-model="time3" range :shortcuts="shortcuts"></date-picker>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
### Attributes
|
### Props
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
| Prop | Type | Default | Description |
|
||||||
|---------------------|---------------|-------------|-----------------------------------------------------|
|
|---------------------|---------------|-------------|-----------------------------------------------------|
|
||||||
| type | String | 'date' | select datepicker or datetimepicker(date/datetime) |
|
| type | String | 'date' | select datepicker or datetimepicker(date/datetime) |
|
||||||
| range | Boolean | false | if true, the type is daterange or datetimerange |
|
| range | Boolean | false | if true, the type is daterange or datetimerange |
|
||||||
| format | String | yyyy-MM-dd | Date formatting string |
|
| format | String | YYYY-MM-DD | The parsing tokens are similar to the moment.js |
|
||||||
| custom-formatter | function | null | custom Date display |
|
|
||||||
| lang | String/Object | zh | Translation (en/zh/es/pt-br/fr/ru/de/it/cs)(custom) |
|
| lang | String/Object | zh | Translation (en/zh/es/pt-br/fr/ru/de/it/cs)(custom) |
|
||||||
|
| clearable | Boolean | true | if false, don't show the clear icon |
|
||||||
| confirm | Boolean | false | if true, need click the button to change the value |
|
| confirm | Boolean | false | if true, need click the button to change the value |
|
||||||
|
| editable | Boolean | true | if false, user cann't type it |
|
||||||
| disabled | Boolean | false | Disable the component |
|
| disabled | Boolean | false | Disable the component |
|
||||||
| editable | Boolean | false | if true, user can type it(only the range is false) |
|
|
||||||
| placeholder | String | | input placeholder text |
|
| placeholder | String | | input placeholder text |
|
||||||
| width | String/Number | 210 | input size |
|
| width | String/Number | 210 | input size |
|
||||||
| disabled-days | Array | [] | Days in YYYY-MM-DD format to disable |
|
|
||||||
| not-before | String/Date | '' | Disable all dates before new Date(not-before) |
|
| not-before | String/Date | '' | Disable all dates before new Date(not-before) |
|
||||||
| not-after | String/Date | '' | Disable all dates after new Date(not-after) |
|
| not-after | String/Date | '' | Disable all dates after new Date(not-after) |
|
||||||
|
| disabled-days | Array/function| [] | Disable Days |
|
||||||
| shortcuts | Boolean/Array | true | the shortcuts for the range picker |
|
| shortcuts | Boolean/Array | true | the shortcuts for the range picker |
|
||||||
| time-picker-options | Object | {} | set timePickerOptions(start, step, end) |
|
| time-picker-options | Object | {} | set timePickerOptions(start, step, end) |
|
||||||
| minute-step | Number | 0 | if > 0 don't show the second picker(0 - 60) |
|
| minute-step | Number | 0 | if > 0 don't show the second picker(0 - 60) |
|
||||||
| first-day-of-week | Number | 7 | set the first day of week (1-7) |
|
| first-day-of-week | Number | 7 | set the first day of week (1-7) |
|
||||||
| input-class | String | 'mx-input' | the input class name |
|
| input-class | String | 'mx-input' | the input class name |
|
||||||
|
| input-name | String | 'date' | the input name attr |
|
||||||
| confirm-text | String | 'OK' | the default text to display on confirm button |
|
| confirm-text | String | 'OK' | the default text to display on confirm button |
|
||||||
| range-separator | String | '~' | the range separator text |
|
| range-separator | String | '~' | the range separator text |
|
||||||
|
|
||||||
|
|
||||||
#### lang
|
#### lang
|
||||||
* String (en/zh/es/pt-br/fr/ru/de/it/cs)
|
* String (en/zh/es/pt-br/fr/ru/de/it/cs)
|
||||||
* Object
|
* Object (custom)
|
||||||
|
|
||||||
```JavaScript
|
```html
|
||||||
{
|
<script>
|
||||||
days: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
export default {
|
||||||
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
data() {
|
||||||
pickers: ['next 7 days', 'next 30 days', 'previous 7 days', 'previous 30 days'],
|
return {
|
||||||
placeholder: {
|
value: '',
|
||||||
date: 'Select Date',
|
lang: {
|
||||||
dateRange: 'Select Date Range'
|
days: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||||
|
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||||
|
pickers: ['next 7 days', 'next 30 days', 'previous 7 days', 'previous 30 days'],
|
||||||
|
placeholder: {
|
||||||
|
date: 'Select Date',
|
||||||
|
dateRange: 'Select Date Range'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<date-picker v-model="value" :lang="lang"></date-picker>
|
||||||
|
</template>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### shortcuts
|
#### shortcuts
|
||||||
@@ -112,8 +144,10 @@ export default {
|
|||||||
### Events
|
### Events
|
||||||
| Name | Description | Callback Arguments |
|
| Name | Description | Callback Arguments |
|
||||||
|-----------------|------------------------------|------------------------|
|
|-----------------|------------------------------|------------------------|
|
||||||
| change | When user select date | the currentValue |
|
| change | When the value change | the currentValue |
|
||||||
|
| input | When the value change | the currentValue |
|
||||||
| confirm | When user click 'OK' button | the currentValue |
|
| confirm | When user click 'OK' button | the currentValue |
|
||||||
|
| input-error | When user type a invalid Date| the input value |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
# vue2-datepicker
|
||||||
|
|
||||||
|
[English Version](https://github.com/mengxiong10/vue2-datepicker/blob/master/README_CN.md)
|
||||||
|
|
||||||
|
> 一个基于Vue2.x的日期时间选择组件
|
||||||
|
|
||||||
|
<a href="https://travis-ci.org/mengxiong10/vue2-datepicker">
|
||||||
|
<img src="https://travis-ci.org/mengxiong10/vue2-datepicker.svg?branch=master" alt="build:passed">
|
||||||
|
</a>
|
||||||
|
<a href="https://coveralls.io/github/mengxiong10/vue2-datepicker">
|
||||||
|
<img src="https://coveralls.io/repos/github/mengxiong10/vue2-datepicker/badge.svg?branch=master" alt="Badge">
|
||||||
|
</a>
|
||||||
|
<a href="LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/License-MIT-yellow.svg">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 线上Demo
|
||||||
|
<https://mengxiong10.github.io/vue2-datepicker/demo>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install vue2-datepicker --save
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用法
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import DatePicker from 'vue2-datepicker'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { DatePicker },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
time1: '',
|
||||||
|
time2: '',
|
||||||
|
time3: '',
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
text: 'Today',
|
||||||
|
start: new Date(),
|
||||||
|
end: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
timePickerOptions:{
|
||||||
|
start: '00:00',
|
||||||
|
step: '00:30',
|
||||||
|
end: '23:30'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<date-picker v-model="time1" :first-day-of-week="1"></date-picker>
|
||||||
|
<date-picker v-model="time2" type="datetime" :time-picker-options="timePickerOptions"></date-picker>
|
||||||
|
<date-picker v-model="time3" range :shortcuts="shortcuts"></date-picker>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description
|
||||||
|
|---------------------|---------------|-------------|-----------------------------------------------------
|
||||||
|
| type | String | 'date' | 选择日期或日期时间(可选:date,datetime)
|
||||||
|
| range | Boolean | false | 如果是true, 显示日历范围选择
|
||||||
|
| format | String | YYYY-MM-DD | 格式化显示日期 api类似moment.js
|
||||||
|
| lang | String/Object | zh | 选择语言或自定义 (en/zh/es/pt-br/fr/ru/de/it/cs)(custom)
|
||||||
|
| clearable | Boolean | true | 如果设置false, 不显示清除图标
|
||||||
|
| confirm | Boolean | false | 如果是true, 显示确认按钮且需要确认才更新时间
|
||||||
|
| editable | Boolean | true | 如果是false, 用户不能手动输入更新日期
|
||||||
|
| disabled | Boolean | false | 禁用组件
|
||||||
|
| placeholder | String | | 输入框placeholder
|
||||||
|
| width | String/Number | 210 | 设置宽度
|
||||||
|
| not-before | String/Date | '' | 禁止选择这个时间之前的时间
|
||||||
|
| not-after | String/Date | '' | 禁止选择这个时间之前=后的时间
|
||||||
|
| disabled-days | Array/function| [] | 自定义禁止的日期
|
||||||
|
| shortcuts | Boolean/Array | true | 自定义范围选择的时候快捷选项(见下表)
|
||||||
|
| time-picker-options | Object | {} | 自定义时间选择的开始,结束,步进(见下表)
|
||||||
|
| minute-step | Number | 0 | 设置分钟的步进, 设置后不显示秒的选择
|
||||||
|
| first-day-of-week | Number | 7 | 设置日历星期几开头(1-7)
|
||||||
|
| input-class | String | 'mx-input' | 自定义输入框的类名
|
||||||
|
| input-name | String | 'date' | 自定义input 的 name 属性
|
||||||
|
| confirm-text | String | 'OK' | 确认按钮的名称
|
||||||
|
| range-separator | String | '~' | range 分隔符
|
||||||
|
|
||||||
|
|
||||||
|
#### shortcuts
|
||||||
|
* true - 显示默认快捷选择
|
||||||
|
* false - 隐藏快捷选择
|
||||||
|
* Object[] - 自定义快捷选择, 格式:[{text, start, end}]
|
||||||
|
|
||||||
|
| 名称 | 类型 | 说明 |
|
||||||
|
|-----------------|---------------|----------------|
|
||||||
|
| text | String | 显示文字 |
|
||||||
|
| start | Date | 开始日期 |
|
||||||
|
| end | Date | 结束日期 |
|
||||||
|
|
||||||
|
#### time-picker-options
|
||||||
|
* Object[] - 自定义时间选择, 格式:[{start, step, end}]
|
||||||
|
|
||||||
|
| 名称 | 类型 | 说明 |
|
||||||
|
|-----------------|---------------|-----------------------|
|
||||||
|
| start | String | 开始时间 (eg '00:00') |
|
||||||
|
| step | String | 步进时间 (eg '00:30') |
|
||||||
|
| end | String | 结束时间 (eg '23:30') |
|
||||||
|
|
||||||
|
|
||||||
|
### Events
|
||||||
|
| Name | 说明 | 回调参数 |
|
||||||
|
|-----------------|----------------------------- |----------------|
|
||||||
|
| change | 日期改变的时候触发 | 选择的日期 |
|
||||||
|
| input | 日期改变的时候触发 | 选择的日期 |
|
||||||
|
| confirm | 点击确认按钮触发的事件 | 选择的日期 |
|
||||||
|
| input-error | 当用户输入的值无效时候触发 | 用户输入的字符串 |
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
[MIT](https://github.com/mengxiong10/vue2-datepicker/blob/master/LICENSE)
|
||||||
|
|
||||||
|
Copyright (c) 2017-present xiemengxiong
|
||||||
-103
@@ -1,103 +0,0 @@
|
|||||||
# vue2-datepicker
|
|
||||||
|
|
||||||
[English Version](https://github.com/mengxiong10/vue2-datepicker/blob/master/README_CN.md)
|
|
||||||
|
|
||||||
> 一个基于Vue2.x的日期时间选择组件
|
|
||||||
|
|
||||||
## 线上Demo
|
|
||||||
<https://mengxiong10.github.io/vue2-datepicker/>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install vue2-datepicker --save
|
|
||||||
```
|
|
||||||
|
|
||||||
## 用法
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script>
|
|
||||||
import DatePicker from 'vue2-datepicker'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { DatePicker },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
time1: '',
|
|
||||||
time2: '',
|
|
||||||
shortcuts: [
|
|
||||||
{
|
|
||||||
text: 'Today',
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<date-picker v-model="time1" :first-day-of-week="1"></date-picker>
|
|
||||||
<date-picker v-model="time2" range :shortcuts="shortcuts"></date-picker>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
### Props
|
|
||||||
|
|
||||||
| 名称 | 类型 | 默认 | 说明
|
|
||||||
|---------------------|---------------|-------------|-------------------------------------------
|
|
||||||
| type | String | 'date' | 选择日期或日期时间(可选:date,datetime)
|
|
||||||
| range | Boolean | false | 如果是true, 显示日历范围选择
|
|
||||||
| confirm | Boolean | false | 如果是true, 显示确认按钮且需要确认才更新时间
|
|
||||||
| format | String | yyyy-MM-dd | 自定义显示在输入框上的格式(yyyy-MM-dd HH:mm:ss)
|
|
||||||
| lang | String | zh | 选择语言 (en/zh/es/pt-br/fr/ru/de/it/cs)
|
|
||||||
| placeholder | String | | placeholder的值
|
|
||||||
| width | String/Number | 210 | 输入框的width
|
|
||||||
| disabled-days | Array | [] | 禁止选择的日期 (['2018-1-1'])
|
|
||||||
| not-before | String/Date | '' | 禁止选择这个时间之前的时间
|
|
||||||
| not-after | String/Date | '' | 禁止选择这个时间之后的时间
|
|
||||||
| shortcuts | Boolean/Array | true | 自定义范围选择的时候快捷选项(见下表)
|
|
||||||
| time-picker-options | Object | {} | 自定义时间选择的开始,结束,步进(见下表)
|
|
||||||
| minute-step | Number | 0 | 分钟的步进,设置time-picker-options,这项无效
|
|
||||||
| first-day-of-week | Number | 7 | 设置日历星期几开头(1-7)
|
|
||||||
| input-class | String | 'mx-input' | 自定义输入框的类名
|
|
||||||
| confirm-text | String | 'OK' | 确认按钮的名称
|
|
||||||
| disabled | Boolean | false | 禁用组件
|
|
||||||
| editable | Boolean | false | 如果是true, 用户可以手动输入 (仅在range === false)
|
|
||||||
|
|
||||||
#### shortcuts
|
|
||||||
* true - 显示默认快捷选择
|
|
||||||
* false - 隐藏快捷选择
|
|
||||||
* Object[] - 自定义快捷选择, 格式:[{text, start, end}]
|
|
||||||
|
|
||||||
| 名称 | 类型 | 说明 |
|
|
||||||
|-----------------|---------------|----------------|
|
|
||||||
| text | String | 显示文字 |
|
|
||||||
| start | Date | 开始日期 |
|
|
||||||
| end | Date | 结束日期 |
|
|
||||||
|
|
||||||
#### time-picker-options
|
|
||||||
* Object[] - 自定义时间选择, 格式:[{start, step, end}]
|
|
||||||
|
|
||||||
| 名称 | 类型 | 说明 |
|
|
||||||
|-----------------|---------------|-----------------------|
|
|
||||||
| start | String | 开始时间 (eg '00:00') |
|
|
||||||
| step | String | 步进时间 (eg '00:30') |
|
|
||||||
| end | String | 结束时间 (eg '23:30') |
|
|
||||||
|
|
||||||
|
|
||||||
### Events
|
|
||||||
| Name | 说明 | 回调参数 |
|
|
||||||
|-----------------|------------------------------|-------------|
|
|
||||||
| change | 选择的时候触发 | 选择的日期 |
|
|
||||||
| confirm | 点击确认按钮触发的事件 | 选择的日期 |
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
[MIT](https://github.com/mengxiong10/vue2-datepicker/blob/master/LICENSE)
|
|
||||||
|
|
||||||
Copyright (c) 2017-present xiemengxiong
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.vue', '.json'],
|
||||||
|
alias: {
|
||||||
|
'@': path.join(__dirname, '..', 'src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.vue$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'vue-loader',
|
||||||
|
options: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test:/\.scss$/,
|
||||||
|
use: ['vue-style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
use: ['babel-loader'],
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpg|gif|svg)$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]?[hash]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new VueLoaderPlugin()
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const merge = require('webpack-merge')
|
||||||
|
const devWebpackConfig = require('./webpack.dev.config.js')
|
||||||
|
|
||||||
|
const webpackConfig = merge(devWebpackConfig, {
|
||||||
|
devtool: 'source-map',
|
||||||
|
mode: 'production'
|
||||||
|
externals: {
|
||||||
|
'vue': 'Vue',
|
||||||
|
'@/index': 'DatePicker'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = webpackConfig
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const merge = require('webpack-merge')
|
||||||
|
const baseWebpackConfig = require('./webpack.base.config.js')
|
||||||
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
||||||
|
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||||
|
|
||||||
|
const webpackConfig = merge(baseWebpackConfig, {
|
||||||
|
mode: 'production',
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../lib'),
|
||||||
|
filename: 'index.js',
|
||||||
|
library: "DatePicker",
|
||||||
|
libraryTarget: "umd"
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new OptimizeCSSPlugin({
|
||||||
|
cssProcessorOptions: { safe: true }
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const webpackConfigExtractCss = merge(baseWebpackConfig, {
|
||||||
|
mode: 'production',
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../lib'),
|
||||||
|
filename: 'datepicker.js',
|
||||||
|
library: "DatePicker",
|
||||||
|
libraryTarget: "umd"
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test:/\.scss$/,
|
||||||
|
use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: 'datepicker.css'
|
||||||
|
}),
|
||||||
|
new OptimizeCSSPlugin({
|
||||||
|
cssProcessorOptions: { safe: true }
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = [ webpackConfig, webpackConfigExtractCss]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const merge = require('webpack-merge')
|
||||||
|
const baseWebpackConfig = require('./webpack.base.config.js')
|
||||||
|
|
||||||
|
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
|
|
||||||
|
const webpackConfig = merge(baseWebpackConfig, {
|
||||||
|
mode: 'development',
|
||||||
|
entry: './demo/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../demo'),
|
||||||
|
filename: 'build.js'
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
'vue': 'Vue'
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
historyApiFallback: {
|
||||||
|
index: './demo/index.html'
|
||||||
|
},
|
||||||
|
noInfo: true,
|
||||||
|
port: 9000
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
hints: false
|
||||||
|
},
|
||||||
|
devtool: 'cheap-module-eval-source-map'
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = webpackConfig
|
||||||
@@ -1,676 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mx-calendar">
|
|
||||||
<div class="mx-calendar-header" v-if="currentPanel === 'time'">
|
|
||||||
<a @click="currentPanel = 'date'">{{now.toLocaleDateString()}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="mx-calendar-header" v-else>
|
|
||||||
<a class="mx-calendar__prev-icon" @click="changeYear(-1)">«</a>
|
|
||||||
<a v-show="currentPanel === 'date'" class="mx-calendar__prev-icon" @click="changeMonth(-1)">‹</a>
|
|
||||||
<a class="mx-calendar__next-icon" @click="changeYear(1)">»</a>
|
|
||||||
<a v-show="currentPanel === 'date'" class="mx-calendar__next-icon" @click="changeMonth(1)">›</a>
|
|
||||||
<a @click="showMonths">{{months[currentMonth]}}</a>
|
|
||||||
<a @click="showYears">{{currentYear}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="mx-calendar-content">
|
|
||||||
<table class="mx-calendar-table" v-show="currentPanel === 'date'">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th v-for="(day, index) in days" :key="index">{{day}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="row in dates">
|
|
||||||
<td v-for="cell in row" :title="cell.title" :class="getDateClasses(cell)" @click="selectDate(cell)">{{cell.day}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="mx-calendar-year" v-show="currentPanel === 'years'">
|
|
||||||
<a v-for="year in years"
|
|
||||||
@click="selectYear(year)"
|
|
||||||
:class="{'current': currentYear === year, 'disabled': isDisabledYear(year)}">{{year}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="mx-calendar-month" v-show="currentPanel === 'months'">
|
|
||||||
<a v-for="(month, index) in months"
|
|
||||||
@click="selectMonth(index)"
|
|
||||||
:class="{'current': currentMonth === index, 'disabled': isDisabledMonth(index)}">{{month}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="mx-calendar-time"
|
|
||||||
v-show="currentPanel === 'time'" >
|
|
||||||
<div v-if="timeSelectOptions.length" class="mx-time-list-wrapper">
|
|
||||||
<ul class="mx-time-list">
|
|
||||||
<li class="mx-time-item mx-time-picker-item"
|
|
||||||
:class="getTimeClasses(item.value.hours * 60 + item.value.minutes, -1)"
|
|
||||||
@click="pickTime(item.value)"
|
|
||||||
v-for="item in timeSelectOptions">
|
|
||||||
{{item.label}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div v-else class="mx-time-list-wrapper"
|
|
||||||
:style="{width: 100 / times.length + '%' }"
|
|
||||||
v-for="(time, index) in times"
|
|
||||||
:key="index">
|
|
||||||
<ul class="mx-time-list">
|
|
||||||
<li class="mx-time-item"
|
|
||||||
v-for="num in time"
|
|
||||||
:class="getTimeClasses(num, index)"
|
|
||||||
:key="num"
|
|
||||||
@click="selectTime(num, index)"
|
|
||||||
>{{num | timeText}}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const getTimeArray = function (len, step = 1) {
|
|
||||||
const length = parseInt(len / step)
|
|
||||||
return Array.apply(null, { length }).map((v, i) => i * step)
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseTime = function(time) {
|
|
||||||
const values = (time || '').split(':');
|
|
||||||
if (values.length >= 2) {
|
|
||||||
const hours = parseInt(values[0], 10);
|
|
||||||
const minutes = parseInt(values[1], 10);
|
|
||||||
return {
|
|
||||||
hours,
|
|
||||||
minutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatTime = function(time, type="24") {
|
|
||||||
let hours = time.hours
|
|
||||||
hours = (type === '24') ? hours : (hours % 12 || 12)
|
|
||||||
hours = hours < 10 ? '0' + hours : hours
|
|
||||||
let minutes = time.minutes < 10 ? '0' + time.minutes : time.minutes
|
|
||||||
let result = hours + ':' + minutes
|
|
||||||
if (type === '12') {
|
|
||||||
result += time.hours >= 12 ? ' pm' : ' am'
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
startAt: null,
|
|
||||||
endAt: null,
|
|
||||||
value: null,
|
|
||||||
show: Boolean
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const translation = this.$parent.translation
|
|
||||||
const minuteStep = this.$parent.minuteStep
|
|
||||||
const times = [getTimeArray(24, 1), getTimeArray(60, minuteStep || 1)]
|
|
||||||
if (minuteStep === 0) {
|
|
||||||
times.push(getTimeArray(60, 1))
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
months: translation.months,
|
|
||||||
dates: [], // 日历面板
|
|
||||||
years: [], // 年代面板
|
|
||||||
now: new Date(), // calendar-header 显示的时间, 用于切换日历
|
|
||||||
currentPanel: 'date',
|
|
||||||
times: times
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 日历显示头
|
|
||||||
days() {
|
|
||||||
const days = this.$parent.translation.days
|
|
||||||
const firstday = +this.$parent.firstDayOfWeek
|
|
||||||
return days.concat(days).slice(firstday, firstday + 7)
|
|
||||||
},
|
|
||||||
timeType () {
|
|
||||||
return /h+/.test(this.$parent.format) ? '12' : '24'
|
|
||||||
},
|
|
||||||
timeSelectOptions () {
|
|
||||||
const result = []
|
|
||||||
const options = this.$parent.timePickerOptions
|
|
||||||
if (!options) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (typeof options === 'function') {
|
|
||||||
return options() || []
|
|
||||||
}
|
|
||||||
const start = parseTime(options.start)
|
|
||||||
const end = parseTime(options.end)
|
|
||||||
const step = parseTime(options.step)
|
|
||||||
if (start && end && step) {
|
|
||||||
const startMinutes = start.minutes + start.hours * 60;
|
|
||||||
const endMinutes = end.minutes + end.hours * 60;
|
|
||||||
const stepMinutes = step.minutes + step.hours * 60
|
|
||||||
const len = Math.floor((endMinutes - startMinutes) / stepMinutes)
|
|
||||||
for (let i = 0; i <= len; i++) {
|
|
||||||
let timeMinutes = startMinutes + i * stepMinutes
|
|
||||||
let hours = Math.floor(timeMinutes/60)
|
|
||||||
let minutes = timeMinutes % 60
|
|
||||||
let value = {
|
|
||||||
hours, minutes
|
|
||||||
}
|
|
||||||
result.push({
|
|
||||||
value,
|
|
||||||
label: formatTime(value, this.timeType)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
currentYear() {
|
|
||||||
return this.now.getFullYear()
|
|
||||||
},
|
|
||||||
currentMonth() {
|
|
||||||
return this.now.getMonth()
|
|
||||||
},
|
|
||||||
curHour() {
|
|
||||||
return this.now.getHours()
|
|
||||||
},
|
|
||||||
curMinute() {
|
|
||||||
return this.now.getMinutes()
|
|
||||||
},
|
|
||||||
curSecond() {
|
|
||||||
return this.now.getSeconds()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.updateCalendar()
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
show(val) {
|
|
||||||
if (val) {
|
|
||||||
this.currentPanel = 'date'
|
|
||||||
this.updateNow()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
handler: 'updateNow',
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
now: 'updateCalendar'
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
timeText(value) {
|
|
||||||
return ('00' + value).slice(String(value).length)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateNow() {
|
|
||||||
this.now = this.value ? new Date(this.value) : new Date()
|
|
||||||
},
|
|
||||||
// 更新面板选择时间
|
|
||||||
updateCalendar() {
|
|
||||||
function getCalendar(time, firstday, length, classes) {
|
|
||||||
return Array.apply(null, { length }).map((v, i) => {
|
|
||||||
// eslint-disable-line
|
|
||||||
let day = firstday + i
|
|
||||||
const date = new Date(
|
|
||||||
time.getFullYear(),
|
|
||||||
time.getMonth(),
|
|
||||||
day,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
date.setDate(day)
|
|
||||||
return {
|
|
||||||
title: date.toLocaleDateString(),
|
|
||||||
date,
|
|
||||||
day,
|
|
||||||
classes
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const firstDayOfWeek = this.$parent.firstDayOfWeek
|
|
||||||
const time = new Date(this.now)
|
|
||||||
time.setDate(0) // 把时间切换到上个月最后一天
|
|
||||||
const lastMonthLength = (time.getDay() + 7 - firstDayOfWeek) % 7 + 1 // time.getDay() 0是星期天, 1是星期一 ...
|
|
||||||
const lastMonthfirst = time.getDate() - (lastMonthLength - 1)
|
|
||||||
const lastMonth = getCalendar(
|
|
||||||
time,
|
|
||||||
lastMonthfirst,
|
|
||||||
lastMonthLength,
|
|
||||||
'lastMonth'
|
|
||||||
)
|
|
||||||
|
|
||||||
time.setMonth(time.getMonth() + 2, 0) // 切换到这个月最后一天
|
|
||||||
const curMonthLength = time.getDate()
|
|
||||||
const curMonth = getCalendar(time, 1, curMonthLength, 'curMonth')
|
|
||||||
|
|
||||||
time.setMonth(time.getMonth() + 1, 1)
|
|
||||||
const nextMonthLength = 42 - (lastMonthLength + curMonthLength)
|
|
||||||
const nextMonth = getCalendar(time, 1, nextMonthLength, 'nextMonth')
|
|
||||||
|
|
||||||
// 分割数组
|
|
||||||
let index = 0
|
|
||||||
let resIndex = 0
|
|
||||||
const arr = lastMonth.concat(curMonth, nextMonth)
|
|
||||||
const result = new Array(6)
|
|
||||||
while (index < 42) {
|
|
||||||
result[resIndex++] = arr.slice(index, (index += 7))
|
|
||||||
}
|
|
||||||
this.dates = result
|
|
||||||
},
|
|
||||||
isDisabled (date) {
|
|
||||||
const now = new Date(date).getTime()
|
|
||||||
if (
|
|
||||||
this.$parent.disabledDays.some(v => new Date(v).setHours(0, 0, 0, 0) === now) ||
|
|
||||||
this.$parent.notBefore !== '' && now < new Date(this.$parent.notBefore).setHours(0, 0, 0, 0) ||
|
|
||||||
this.$parent.notAfter !== '' && now > new Date(this.$parent.notAfter).setHours(0, 0, 0, 0) ||
|
|
||||||
this.startAt && now < new Date(this.startAt).setHours(0, 0, 0, 0) ||
|
|
||||||
this.endAt && now > new Date(this.endAt).setHours(0, 0, 0, 0)
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
getDateClasses(cell) {
|
|
||||||
const classes = []
|
|
||||||
const cellTime = new Date(cell.date).setHours(0, 0, 0, 0)
|
|
||||||
const cellEndTime = new Date(cell.date).setHours(23, 59, 59, 999)
|
|
||||||
const curTime = this.value ? new Date(this.value).setHours(0, 0, 0, 0) : 0
|
|
||||||
const startTime = this.startAt
|
|
||||||
? new Date(this.startAt).setHours(0, 0, 0, 0)
|
|
||||||
: 0
|
|
||||||
const endTime = this.endAt ? new Date(this.endAt).setHours(0, 0, 0, 0) : 0
|
|
||||||
const today = new Date().setHours(0, 0, 0, 0)
|
|
||||||
if (this.isDisabled(cellTime)) {
|
|
||||||
return 'disabled'
|
|
||||||
}
|
|
||||||
classes.push(cell.classes)
|
|
||||||
|
|
||||||
if (cellTime === today) {
|
|
||||||
classes.push('today')
|
|
||||||
}
|
|
||||||
|
|
||||||
// range classes
|
|
||||||
if (curTime) {
|
|
||||||
if (cellTime === curTime) {
|
|
||||||
classes.push('current')
|
|
||||||
} else if (startTime && cellTime <= curTime) {
|
|
||||||
classes.push('inrange')
|
|
||||||
} else if (endTime && cellTime >= curTime) {
|
|
||||||
classes.push('inrange')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return classes.join(' ')
|
|
||||||
},
|
|
||||||
getTimeClasses(value, index) {
|
|
||||||
let curValue
|
|
||||||
let cellTime
|
|
||||||
const startTime = this.startAt ? new Date(this.startAt) : 0
|
|
||||||
const endTime = this.endAt ? new Date(this.endAt) : 0
|
|
||||||
const classes = []
|
|
||||||
switch (index) {
|
|
||||||
case -1:
|
|
||||||
curValue = this.curHour * 60 + this.curMinute
|
|
||||||
cellTime = new Date(this.now).setHours(Math.floor(value / 60), value % 60, 0)
|
|
||||||
break
|
|
||||||
case 0:
|
|
||||||
curValue = this.curHour
|
|
||||||
cellTime = new Date(this.now).setHours(value)
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
curValue = this.curMinute
|
|
||||||
cellTime = new Date(this.now).setMinutes(value)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
curValue = this.curSecond
|
|
||||||
cellTime = new Date(this.now).setSeconds(value)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(this.$parent.notBefore !== '' &&
|
|
||||||
cellTime < new Date(this.$parent.notBefore).getTime()) ||
|
|
||||||
(this.$parent.notAfter !== '' &&
|
|
||||||
cellTime > new Date(this.$parent.notAfter).getTime())
|
|
||||||
) {
|
|
||||||
return 'disabled'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === curValue) {
|
|
||||||
classes.push('cur-time')
|
|
||||||
} else if (startTime) {
|
|
||||||
if (cellTime < startTime) {
|
|
||||||
classes.push('disabled')
|
|
||||||
}
|
|
||||||
} else if (endTime) {
|
|
||||||
if (cellTime > endTime) {
|
|
||||||
classes.push('disabled')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return classes.join(' ')
|
|
||||||
},
|
|
||||||
showMonths() {
|
|
||||||
if (this.currentPanel === 'months') {
|
|
||||||
this.currentPanel = 'date'
|
|
||||||
} else {
|
|
||||||
this.currentPanel = 'months'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showYears() {
|
|
||||||
// 当前年代
|
|
||||||
if (this.currentPanel === 'years') {
|
|
||||||
this.currentPanel = 'date'
|
|
||||||
} else {
|
|
||||||
let firstYear = Math.floor(this.now.getFullYear() / 10) * 10
|
|
||||||
let years = []
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
years.push(firstYear + i)
|
|
||||||
}
|
|
||||||
this.years = years
|
|
||||||
this.currentPanel = 'years'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 前进或后退一年
|
|
||||||
changeYear(flag) {
|
|
||||||
if (this.currentPanel === 'years') {
|
|
||||||
this.years = this.years.map(v => v + flag * 10)
|
|
||||||
} else {
|
|
||||||
const now = new Date(this.now)
|
|
||||||
now.setFullYear(now.getFullYear() + flag, now.getMonth(), 1)
|
|
||||||
this.now = now
|
|
||||||
}
|
|
||||||
},
|
|
||||||
changeMonth(flag) {
|
|
||||||
const now = new Date(this.now)
|
|
||||||
now.setMonth(now.getMonth() + flag, 1)
|
|
||||||
this.now = now
|
|
||||||
},
|
|
||||||
scrollIntoView(container, selected) {
|
|
||||||
if (!selected) {
|
|
||||||
container.scrollTop = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const top = selected.offsetTop
|
|
||||||
const bottom = selected.offsetTop + selected.offsetHeight
|
|
||||||
const viewRectTop = container.scrollTop
|
|
||||||
const viewRectBottom = viewRectTop + container.clientHeight
|
|
||||||
if (top < viewRectTop) {
|
|
||||||
container.scrollTop = top
|
|
||||||
} else if (bottom > viewRectBottom) {
|
|
||||||
container.scrollTop = bottom - container.clientHeight
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectDate(cell) {
|
|
||||||
const classes = this.getDateClasses(cell)
|
|
||||||
if (classes.indexOf('disabled') !== -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let date = new Date(cell.date)
|
|
||||||
// datetime 跳转到 timepicker
|
|
||||||
if (this.$parent.type === 'datetime') {
|
|
||||||
// 保留时分秒
|
|
||||||
if (this.value instanceof Date) {
|
|
||||||
date.setHours(
|
|
||||||
this.value.getHours(),
|
|
||||||
this.value.getMinutes(),
|
|
||||||
this.value.getSeconds()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (this.startAt && date.getTime() < new Date(this.startAt).getTime()) {
|
|
||||||
date = new Date(this.startAt)
|
|
||||||
} else if (
|
|
||||||
this.endAt &&
|
|
||||||
date.getTime() > new Date(this.endAt).getTime()
|
|
||||||
) {
|
|
||||||
date = new Date(this.endAt)
|
|
||||||
}
|
|
||||||
this.currentPanel = 'time'
|
|
||||||
this.$nextTick(() => {
|
|
||||||
Array.prototype.forEach.call(
|
|
||||||
this.$el.querySelectorAll('.mx-time-list-wrapper'),
|
|
||||||
(el) => {
|
|
||||||
this.scrollIntoView(el, el.querySelector('.cur-time'))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.now = date
|
|
||||||
this.$emit('input', date)
|
|
||||||
this.$emit('select')
|
|
||||||
},
|
|
||||||
isDisabledYear (year) {
|
|
||||||
if (this.value) {
|
|
||||||
const now = new Date(this.now).setFullYear(year)
|
|
||||||
return this.isDisabled(now)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
isDisabledMonth (month) {
|
|
||||||
if (this.value) {
|
|
||||||
const now = new Date(this.now).setMonth(month)
|
|
||||||
return this.isDisabled(now)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
selectYear(year) {
|
|
||||||
if (this.isDisabledYear(year)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const now = new Date(this.now)
|
|
||||||
now.setFullYear(year)
|
|
||||||
this.now = now
|
|
||||||
if (this.value) {
|
|
||||||
this.$emit('input', now)
|
|
||||||
this.$emit('select', true)
|
|
||||||
}
|
|
||||||
this.currentPanel = 'months'
|
|
||||||
},
|
|
||||||
selectMonth(month) {
|
|
||||||
if (this.isDisabledMonth(month)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const now = new Date(this.now)
|
|
||||||
now.setMonth(month)
|
|
||||||
this.now = now
|
|
||||||
if (this.value) {
|
|
||||||
this.$emit('input', now)
|
|
||||||
this.$emit('select', true)
|
|
||||||
}
|
|
||||||
this.currentPanel = 'date'
|
|
||||||
},
|
|
||||||
selectTime(value, index) {
|
|
||||||
const classes = this.getTimeClasses(value, index)
|
|
||||||
if (classes.indexOf('disabled') !== -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const date = new Date(this.now)
|
|
||||||
if (index === 0) {
|
|
||||||
date.setHours(value)
|
|
||||||
} else if (index === 1) {
|
|
||||||
date.setMinutes(value)
|
|
||||||
} else if (index === 2) {
|
|
||||||
date.setSeconds(value)
|
|
||||||
}
|
|
||||||
this.now = date
|
|
||||||
this.$emit('input', date)
|
|
||||||
this.$emit('select')
|
|
||||||
},
|
|
||||||
pickTime (value) {
|
|
||||||
const classes = this.getTimeClasses(value.hours * 60 + value.minutes, -1)
|
|
||||||
if (classes.indexOf('disabled') !== -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const date = new Date(this.now)
|
|
||||||
date.setHours(value.hours, value.minutes, 0)
|
|
||||||
this.now = date
|
|
||||||
this.$emit('input', date)
|
|
||||||
this.$emit('select')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.mx-calendar {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
padding: 6px 12px;
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.mx-calendar-header {
|
|
||||||
line-height: 34px;
|
|
||||||
text-align: center;
|
|
||||||
& > a:hover {
|
|
||||||
color: #1284e7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar__next-icon,
|
|
||||||
.mx-calendar__prev-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
padding: 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar__prev-icon {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar__next-icon {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-content {
|
|
||||||
height: 224px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-table {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
table-layout: fixed;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
td {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.today {
|
|
||||||
color: #20a0ff;
|
|
||||||
}
|
|
||||||
.lastMonth,
|
|
||||||
.nextMonth {
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-table td,
|
|
||||||
.mx-calendar-table th {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-table td {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-table td.inrange,
|
|
||||||
.mx-calendar-table td:hover,
|
|
||||||
.mx-calendar-year > a:hover,
|
|
||||||
.mx-calendar-month > a:hover {
|
|
||||||
background-color: #eaf8fe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-table td.current,
|
|
||||||
.mx-calendar-year > a.current,
|
|
||||||
.mx-calendar-month > a.current {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1284e7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-table td.disabled,
|
|
||||||
.mx-time-item.disabled,
|
|
||||||
.mx-calendar-year a.disabled,
|
|
||||||
.mx-calendar-month a.disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
color: #ccc;
|
|
||||||
background-color: #f3f3f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-year,
|
|
||||||
.mx-calendar-month,
|
|
||||||
.mx-calendar-time {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 7px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-year > a {
|
|
||||||
display: inline-block;
|
|
||||||
width: 40%;
|
|
||||||
margin: 1px 5%;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-calendar-month > a {
|
|
||||||
display: inline-block;
|
|
||||||
width: 30%;
|
|
||||||
line-height: 40px;
|
|
||||||
margin: 8px 1.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-time-list-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
border-left: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-time-list-wrapper::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动条滑块 */
|
|
||||||
.mx-time-list-wrapper::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-time-list-wrapper:hover::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-time-list-wrapper:first-child {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
.mx-time-picker-item {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-time-list {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
.mx-time-item {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.mx-time-item:hover {
|
|
||||||
background-color: #eaf8fe;
|
|
||||||
}
|
|
||||||
.mx-time-item.cur-time {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1284e7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,583 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mx-datepicker"
|
|
||||||
:class="{'disabled': disabled}"
|
|
||||||
:style="{'width': computedWidth,'min-width':range ? (type === 'datetime' ? '320px' : '210px') : '140px'}"
|
|
||||||
v-clickoutside="closePopup">
|
|
||||||
<input :name="inputName"
|
|
||||||
:disabled="disabled"
|
|
||||||
:class="inputClass"
|
|
||||||
:value="text"
|
|
||||||
:readonly="!editable || range"
|
|
||||||
:placeholder="innerPlaceholder"
|
|
||||||
ref="input"
|
|
||||||
@mouseenter="hoverIcon"
|
|
||||||
@mouseleave="hoverIcon"
|
|
||||||
@click="togglePopup"
|
|
||||||
@input="handleInput"
|
|
||||||
@change="handleChange"
|
|
||||||
@mousedown="$event.preventDefault()">
|
|
||||||
<i class="mx-input-icon"
|
|
||||||
:class="showCloseIcon ? 'mx-input-icon__close' : 'mx-input-icon__calendar'"
|
|
||||||
@mouseenter="hoverIcon"
|
|
||||||
@mouseleave="hoverIcon"
|
|
||||||
@click="clickIcon"></i>
|
|
||||||
<div class="mx-datepicker-popup"
|
|
||||||
:class="{'range':range}"
|
|
||||||
:style="position"
|
|
||||||
ref="calendar"
|
|
||||||
v-show="showPopup">
|
|
||||||
<calendar-panel v-if="!range"
|
|
||||||
v-model="currentValue"
|
|
||||||
@select="selectDate"
|
|
||||||
:show="showPopup"></calendar-panel>
|
|
||||||
<div v-else
|
|
||||||
style="overflow:hidden">
|
|
||||||
<div class="mx-datepicker-top"
|
|
||||||
v-if="ranges.length">
|
|
||||||
<span v-for="range in ranges"
|
|
||||||
@click="selectRange(range)">{{range.text}}</span>
|
|
||||||
</div>
|
|
||||||
<calendar-panel style="width:50%;box-shadow:1px 0 rgba(0, 0, 0, .1)"
|
|
||||||
v-model="currentValue[0]"
|
|
||||||
:end-at="currentValue[1]"
|
|
||||||
@select="selectDate"
|
|
||||||
:show="showPopup"></calendar-panel>
|
|
||||||
<calendar-panel style="width:50%;"
|
|
||||||
v-model="currentValue[1]"
|
|
||||||
:start-at="currentValue[0]"
|
|
||||||
@select="selectDate"
|
|
||||||
:show="showPopup"></calendar-panel>
|
|
||||||
</div>
|
|
||||||
<div class="mx-datepicker-footer"
|
|
||||||
v-if="confirm">
|
|
||||||
<button type="button"
|
|
||||||
class="mx-datepicker-btn mx-datepicker-btn-confirm"
|
|
||||||
@click="confirmDate"> {{ confirmText }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import CalendarPanel from './calendar-panel.vue'
|
|
||||||
import Languages from './languages.js'
|
|
||||||
|
|
||||||
const isObject = function (obj) {
|
|
||||||
return obj !== null && typeof obj === 'object'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'DatePicker',
|
|
||||||
components: { CalendarPanel },
|
|
||||||
props: {
|
|
||||||
value: null,
|
|
||||||
format: {
|
|
||||||
type: String,
|
|
||||||
default: 'yyyy-MM-dd'
|
|
||||||
},
|
|
||||||
customFormatter: {
|
|
||||||
type: Function
|
|
||||||
},
|
|
||||||
range: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'date' // ['date', 'datetime']
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: 210
|
|
||||||
},
|
|
||||||
placeholder: String,
|
|
||||||
lang: {
|
|
||||||
type: [String, Object],
|
|
||||||
default: 'zh'
|
|
||||||
},
|
|
||||||
shortcuts: {
|
|
||||||
type: [Boolean, Array],
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
disabledDays: {
|
|
||||||
type: Array,
|
|
||||||
default: function () {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
notBefore: {
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
notAfter: {
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
firstDayOfWeek: {
|
|
||||||
default: 7,
|
|
||||||
type: Number,
|
|
||||||
validator: val => val >= 1 && val <= 7
|
|
||||||
},
|
|
||||||
minuteStep: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
validator: val => val >= 0 && val <= 60
|
|
||||||
},
|
|
||||||
timePickerOptions: {
|
|
||||||
type: [Object, Function],
|
|
||||||
default () {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirm: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
inputClass: {
|
|
||||||
type: String,
|
|
||||||
default: 'mx-input'
|
|
||||||
},
|
|
||||||
confirmText: {
|
|
||||||
type: String,
|
|
||||||
default: 'OK'
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
editable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
rangeSeparator: {
|
|
||||||
type: String,
|
|
||||||
default: '~'
|
|
||||||
},
|
|
||||||
inputName:{
|
|
||||||
type: String,
|
|
||||||
default: 'date'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
showPopup: false,
|
|
||||||
showCloseIcon: false,
|
|
||||||
currentValue: this.value,
|
|
||||||
position: null,
|
|
||||||
userInput: null,
|
|
||||||
ranges: [] // 快捷选项
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
value: {
|
|
||||||
handler (val) {
|
|
||||||
if (!this.range) {
|
|
||||||
this.currentValue = this.isValidDate(val) ? val : undefined
|
|
||||||
} else {
|
|
||||||
this.currentValue = this.isValidRange(val)
|
|
||||||
? val.slice(0, 2)
|
|
||||||
: [undefined, undefined]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
showPopup (val) {
|
|
||||||
if (val) {
|
|
||||||
this.$nextTick(this.displayPopup)
|
|
||||||
} else {
|
|
||||||
this.userInput = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
translation () {
|
|
||||||
if (isObject(this.lang)) {
|
|
||||||
return { ...Languages['en'], ...this.lang }
|
|
||||||
}
|
|
||||||
return Languages[this.lang] || Languages['en']
|
|
||||||
},
|
|
||||||
innerPlaceholder () {
|
|
||||||
return (
|
|
||||||
this.placeholder ||
|
|
||||||
(this.range
|
|
||||||
? this.translation.placeholder.dateRange
|
|
||||||
: this.translation.placeholder.date)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text () {
|
|
||||||
if (!this.range && this.isValidDate(this.value)) {
|
|
||||||
return this.userInput !== null ? this.userInput : this.stringify(this.value)
|
|
||||||
}
|
|
||||||
if (this.range && this.isValidRange(this.value)) {
|
|
||||||
return (
|
|
||||||
this.stringify(this.value[0]) + ` ${this.rangeSeparator} ` + this.stringify(this.value[1])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
computedWidth () {
|
|
||||||
if ((typeof this.width === 'string' && /^\d+$/.test(this.width)) || typeof this.width === 'number') {
|
|
||||||
return this.width + 'px'
|
|
||||||
}
|
|
||||||
return this.width
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleInput (event) {
|
|
||||||
this.userInput = event.target.value
|
|
||||||
},
|
|
||||||
handleChange (event) {
|
|
||||||
const value = event.target.value
|
|
||||||
const date = this.parseDate(value, this.format)
|
|
||||||
if (date && this.editable && !this.range) {
|
|
||||||
if (this.notBefore && date < new Date(this.notBefore)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.notAfter && date > new Date(this.notAfter)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (let i = 0, len = this.disabledDays.length; i < len; i++) {
|
|
||||||
if (date.getTime() === new Date(this.disabledDays[i]).getTime()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$emit('input', date)
|
|
||||||
this.$emit('change', date)
|
|
||||||
this.closePopup()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateDate () {
|
|
||||||
const val = this.currentValue
|
|
||||||
if ((!this.range && val) || (this.range && val[0] && val[1])) {
|
|
||||||
this.$emit('input', val)
|
|
||||||
this.$emit('change', val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmDate () {
|
|
||||||
this.updateDate()
|
|
||||||
this.closePopup()
|
|
||||||
this.$emit('confirm', this.currentValue)
|
|
||||||
},
|
|
||||||
selectDate (show = false) {
|
|
||||||
if (!this.confirm && !this.disabled) {
|
|
||||||
this.updateDate()
|
|
||||||
if (!show && this.type === 'date' && !this.range) {
|
|
||||||
this.closePopup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closePopup () {
|
|
||||||
this.showPopup = false
|
|
||||||
},
|
|
||||||
togglePopup () {
|
|
||||||
if (this.showPopup) {
|
|
||||||
this.$refs.input.blur()
|
|
||||||
this.showPopup = false
|
|
||||||
} else {
|
|
||||||
this.$refs.input.focus()
|
|
||||||
this.showPopup = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hoverIcon (e) {
|
|
||||||
if (this.disabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.type === 'mouseenter' && this.text) {
|
|
||||||
this.showCloseIcon = true
|
|
||||||
}
|
|
||||||
if (e.type === 'mouseleave') {
|
|
||||||
this.showCloseIcon = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clickIcon () {
|
|
||||||
if (this.disabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.showCloseIcon) {
|
|
||||||
this.$emit('input', '')
|
|
||||||
this.$emit('change', '')
|
|
||||||
} else {
|
|
||||||
this.togglePopup()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parseDate (str, fmt = 'yyyy-MM-dd') {
|
|
||||||
let isValid = true
|
|
||||||
const obj = { y: 0, M: 1, d: 0, H: 0, h: 0, m: 0, s: 0 }
|
|
||||||
fmt.replace(/([^yMdHhms]*?)(([yMdHhms])\3*)([^yMdHhms]*?)/g, function (m, $1, $2, $3, $4, idx, old) {
|
|
||||||
const rgs = new RegExp($1 + '(\\d{' + $2.length + '})' + $4)
|
|
||||||
const index = str.search(rgs)
|
|
||||||
if (index === -1) {
|
|
||||||
isValid = false
|
|
||||||
} else {
|
|
||||||
str = str.replace(rgs, function (_m, _$1) {
|
|
||||||
obj[$3] = parseInt(_$1);
|
|
||||||
return ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
});
|
|
||||||
if (!isValid) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
obj.M--
|
|
||||||
return new Date(obj.y, obj.M, obj.d, obj.H || obj.h, obj.m, obj.s)
|
|
||||||
},
|
|
||||||
formatDate (date, fmt = 'yyyy-MM-dd HH:mm:ss') {
|
|
||||||
const hour = date.getHours()
|
|
||||||
const map = {
|
|
||||||
'M+': date.getMonth() + 1, // 月份
|
|
||||||
'[Dd]+': date.getDate(), // 日
|
|
||||||
'H+': hour, // 小时
|
|
||||||
'h+': (hour % 12) || 12, // 小时
|
|
||||||
'm+': date.getMinutes(), // 分
|
|
||||||
's+': date.getSeconds(), // 秒
|
|
||||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
|
||||||
S: date.getMilliseconds(), // 毫秒
|
|
||||||
'a': hour >= 12 ? 'pm' : 'am',
|
|
||||||
'A': hour >= 12 ? 'PM' : 'AM'
|
|
||||||
}
|
|
||||||
let str = fmt.replace(/[Yy]+/g, function (str) {
|
|
||||||
return ('' + date.getFullYear()).slice(4 - str.length)
|
|
||||||
})
|
|
||||||
Object.keys(map).forEach(key => {
|
|
||||||
str = str.replace(new RegExp(key), function (str) {
|
|
||||||
const value = '' + map[key]
|
|
||||||
return str.length === 1 ? value : ('00' + value).slice(value.length)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return str
|
|
||||||
},
|
|
||||||
stringify (date) {
|
|
||||||
if (typeof this.customFormatter === 'function') {
|
|
||||||
return this.customFormatter(new Date(date))
|
|
||||||
}
|
|
||||||
return this.formatDate(new Date(date), this.format)
|
|
||||||
},
|
|
||||||
isValidDate (date) {
|
|
||||||
return !!new Date(date).getTime()
|
|
||||||
},
|
|
||||||
isValidRange (date) {
|
|
||||||
return (
|
|
||||||
Array.isArray(date) &&
|
|
||||||
date.length === 2 &&
|
|
||||||
this.isValidDate(date[0]) &&
|
|
||||||
this.isValidDate(date[1])
|
|
||||||
)
|
|
||||||
},
|
|
||||||
selectRange (range) {
|
|
||||||
this.$emit('input', [range.start, range.end])
|
|
||||||
this.$emit('change', [range.start, range.end])
|
|
||||||
},
|
|
||||||
initRanges () {
|
|
||||||
if (Array.isArray(this.shortcuts)) {
|
|
||||||
this.ranges = this.shortcuts
|
|
||||||
} else if (this.shortcuts) {
|
|
||||||
this.ranges = [
|
|
||||||
{
|
|
||||||
text: '未来7天',
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date(Date.now() + 3600 * 1000 * 24 * 7)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '未来30天',
|
|
||||||
start: new Date(),
|
|
||||||
end: new Date(Date.now() + 3600 * 1000 * 24 * 30)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '最近7天',
|
|
||||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 7),
|
|
||||||
end: new Date()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '最近30天',
|
|
||||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 30),
|
|
||||||
end: new Date()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
this.ranges.forEach((v, i) => {
|
|
||||||
v.text = this.translation.pickers[i]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.ranges = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
displayPopup () {
|
|
||||||
if (this.disabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const dw = document.documentElement.clientWidth
|
|
||||||
const dh = document.documentElement.clientHeight
|
|
||||||
const InputRect = this.$el.getBoundingClientRect()
|
|
||||||
const PopupRect = this.$refs.calendar.getBoundingClientRect()
|
|
||||||
this.position = {}
|
|
||||||
if (
|
|
||||||
dw - InputRect.left < PopupRect.width &&
|
|
||||||
InputRect.right < PopupRect.width
|
|
||||||
) {
|
|
||||||
this.position.left = 1 - InputRect.left + 'px'
|
|
||||||
} else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
|
|
||||||
this.position.left = 0
|
|
||||||
} else {
|
|
||||||
this.position.right = 0
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
InputRect.top <= PopupRect.height + 1 &&
|
|
||||||
dh - InputRect.bottom <= PopupRect.height + 1
|
|
||||||
) {
|
|
||||||
this.position.top = dh - InputRect.top - PopupRect.height - 1 + 'px'
|
|
||||||
} else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
|
|
||||||
this.position.top = '100%'
|
|
||||||
} else {
|
|
||||||
this.position.bottom = '100%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.initRanges()
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
clickoutside: {
|
|
||||||
bind (el, binding, vnode) {
|
|
||||||
el['@clickoutside'] = e => {
|
|
||||||
if (
|
|
||||||
!el.contains(e.target) &&
|
|
||||||
binding.expression &&
|
|
||||||
vnode.context[binding.expression]
|
|
||||||
) {
|
|
||||||
binding.value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('click', el['@clickoutside'], true)
|
|
||||||
},
|
|
||||||
unbind (el) {
|
|
||||||
document.removeEventListener('click', el['@clickoutside'], true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.mx-datepicker {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
color: #73879c;
|
|
||||||
font: 14px/1.5 'Helvetica Neue', Helvetica, Arial, 'Microsoft Yahei',
|
|
||||||
sans-serif;
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.mx-datepicker-popup {
|
|
||||||
position: absolute;
|
|
||||||
width: 250px;
|
|
||||||
margin-top: 1px;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
|
||||||
z-index: 1000;
|
|
||||||
&.range {
|
|
||||||
width: 496px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-input {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
height: 34px;
|
|
||||||
padding: 6px 30px 6px 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: #555;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
|
||||||
&:disabled,
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-input-icon {
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
position: absolute;
|
|
||||||
width: 30px;
|
|
||||||
height: 100%;
|
|
||||||
color: #888;
|
|
||||||
text-align: center;
|
|
||||||
font-style: normal;
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
width: 0;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-input-icon__calendar {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA00lEQVQ4T72SzQ2CQBCF54UGKIES6EAswQq0BS/A3PQ0hAt0oKVQgiVYAkcuZMwSMOyCyRKNe9uf+d6b2Qf6csGtL8sy7vu+Zebn/E5EoiAIwjRNH/PzBUBEGiJqmPniAMw+YeZkFSAiJwA3j45aVT0wsxGitwOjDGDnASBVvU4OLQARRURk9e4CAcSqWn8CLHp3Ae6MXAe/B4yzUeMkz/P9ZgdFUQzFIwD/B4yKgwMTos0OtvzCHcDRJ0gAzlmW1VYSq6oKu66LfQBTjC2AT+Hamxcml5IRpPq3VQAAAABJRU5ErkJggg==);
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
.mx-input-icon__close::before {
|
|
||||||
content: '\2716';
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-datepicker-top {
|
|
||||||
text-align: left;
|
|
||||||
padding: 0 12px;
|
|
||||||
line-height: 34px;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
& > span {
|
|
||||||
white-space: nowrap;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
color: #1284e7;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
content: '|';
|
|
||||||
margin: 0 10px;
|
|
||||||
color: #48576a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-datepicker-footer {
|
|
||||||
padding: 4px;
|
|
||||||
clear: both;
|
|
||||||
text-align: right;
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.mx-datepicker-btn {
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 7px 15px;
|
|
||||||
margin: 0 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.mx-datepicker-btn-confirm {
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
color: #73879c;
|
|
||||||
&:hover {
|
|
||||||
color: #1284e7;
|
|
||||||
border-color: #1284e7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
-136
@@ -1,136 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="app">
|
|
||||||
<div class="example">
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">default:</span>
|
|
||||||
<date-picker v-model="value1" lang="en" editable></date-picker>
|
|
||||||
</section>
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">range:</span>
|
|
||||||
<date-picker v-model="value2" range lang="en"></date-picker>
|
|
||||||
</section>
|
|
||||||
<pre class="pre">{{demo1}}</pre>
|
|
||||||
</div>
|
|
||||||
<div class="example">
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">datetime:</span>
|
|
||||||
<date-picker v-model="value3" lang="en" type="datetime" format="yyyy-MM-dd HH:mm:ss"></date-picker>
|
|
||||||
</section>
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">datetime with minute-step picker:</span>
|
|
||||||
<date-picker v-model="value4" lang="en" type="datetime" format="yyyy-MM-dd hh:mm:ss a"
|
|
||||||
:time-picker-options="{
|
|
||||||
start: '00:00',
|
|
||||||
step: '00:30',
|
|
||||||
end: '23:30'
|
|
||||||
}"></date-picker>
|
|
||||||
</section>
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">datetime range:</span>
|
|
||||||
<date-picker v-model="value5" range type="datetime" lang="en" format="yyyy-MM-dd HH:mm:ss"></date-picker>
|
|
||||||
</section>
|
|
||||||
<blockquote class="tips">
|
|
||||||
if you use the datetime, you should set the format which default is 'yyyy-MM-dd'
|
|
||||||
</blockquote>
|
|
||||||
<pre class="pre">{{demo2}}</pre>
|
|
||||||
</div>
|
|
||||||
<div class="example">
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">with confirm:</span>
|
|
||||||
<date-picker
|
|
||||||
v-model="value6"
|
|
||||||
format="yyyy-MM-dd"
|
|
||||||
lang="en"
|
|
||||||
confirm></date-picker>
|
|
||||||
</section>
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">datetime with confirm:</span>
|
|
||||||
<date-picker
|
|
||||||
v-model="value7"
|
|
||||||
type="datetime"
|
|
||||||
lang="en"
|
|
||||||
format="yyyy-MM-dd hh:mm:ss"
|
|
||||||
confirm></date-picker>
|
|
||||||
</section>
|
|
||||||
<section class="demo">
|
|
||||||
<span class="label">range with confirm:</span>
|
|
||||||
<date-picker
|
|
||||||
v-model="value8"
|
|
||||||
range
|
|
||||||
lang="en"
|
|
||||||
format="yyyy-MM-dd"
|
|
||||||
@confirm="confirm"
|
|
||||||
confirm></date-picker>
|
|
||||||
</section>
|
|
||||||
<blockquote class="tips">
|
|
||||||
Recommend to use the confirm option when the type is 'datetime' or range is true
|
|
||||||
</blockquote>
|
|
||||||
<pre class="pre">{{demo3}}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import DatePicker from '../index'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'app',
|
|
||||||
components: { DatePicker },
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
value1: new Date(),
|
|
||||||
value2: '',
|
|
||||||
value3: new Date(),
|
|
||||||
value4: '',
|
|
||||||
value5: '',
|
|
||||||
value6: '',
|
|
||||||
value7: '',
|
|
||||||
value8: '',
|
|
||||||
demo1: '<date-picker v-model="value1" editable lang="en"></date-picker>\n<date-picker v-model="value3" range lang="en"></date-picker>',
|
|
||||||
demo2: `<date-picker v-model="value3" type="datetime" format="yyyy-MM-dd HH:mm:ss" lang="en"></date-picker>\n<date-picker v-model="value4" type="datetime" format="yyyy-MM-dd hh:mm:ss a" :time-picker-options="{start: '00:00',step: '00:30',end: '23:30'}" lang="en"></date-picker>\n<date-picker v-model="value4" range type="datetime" format="yyyy-MM-dd HH:mm:ss" lang="en"></date-picker>`,
|
|
||||||
demo3: '<date-picker v-model="value6" format="yyyy-MM-dd" lang="en" confirm></date-picker>\n<date-picker v-model="value7" lang="en" type="datetime" format="yyyy-MM-dd hh:mm:ss" confirm></date-picker>\n<date-picker v-model="value8" lang="en" range format="yyyy-MM-dd" confirm></date-picker>'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
confirm (val) {
|
|
||||||
console.log(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.demo {
|
|
||||||
margin:20px;
|
|
||||||
}
|
|
||||||
.label{
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
.pre {
|
|
||||||
padding: 16px;
|
|
||||||
overflow: auto;
|
|
||||||
font-size: 85%;
|
|
||||||
line-height: 1.45;
|
|
||||||
background-color: #f6f8fa;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.example {
|
|
||||||
padding: 45px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-bottom-right-radius: 3px;
|
|
||||||
border-bottom-left-radius: 3px;
|
|
||||||
}
|
|
||||||
.example > .demo {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.tips {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 1em;
|
|
||||||
color: #6a737d;
|
|
||||||
border-left: 0.25em solid #dfe2e5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>vue2-datepicker</title>
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
.example {
|
||||||
|
padding: 20px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
border-bottom-left-radius: 3px;
|
||||||
|
}
|
||||||
|
.source {
|
||||||
|
display: inline-block;
|
||||||
|
margin:20px;
|
||||||
|
}
|
||||||
|
.label{
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
.pre {
|
||||||
|
padding: 8px;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.4;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
color: #6a737d;
|
||||||
|
border-left: 0.25em solid #dfe2e5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
|
||||||
|
<script src="https://unpkg.com/vue2-datepicker/dist/build.js"></script>
|
||||||
|
<script src="./build.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||||
|
<script>window.hljs.initHighlightingOnLoad();</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import DatePicker from '@/index'
|
||||||
|
|
||||||
|
Vue.use(DatePicker)
|
||||||
|
|
||||||
|
new Vue({ // eslint-disable-line
|
||||||
|
el: '#app',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
value1: new Date(),
|
||||||
|
value2: '',
|
||||||
|
value3: new Date(),
|
||||||
|
value4: '',
|
||||||
|
value5: '',
|
||||||
|
value6: '',
|
||||||
|
value7: '',
|
||||||
|
value8: '',
|
||||||
|
value9: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getSource (obj) {
|
||||||
|
return Object.keys(obj).map(key => {
|
||||||
|
const value = obj[key]
|
||||||
|
return (
|
||||||
|
<section class="source">
|
||||||
|
<label class="label">{key} : </label>
|
||||||
|
{Vue.compile(value).render.call(this)}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPre (obj) {
|
||||||
|
return Object.keys(obj).map(key => {
|
||||||
|
const value = obj[key].replace(/\n/g, '').replace(/\s+/g, ' ')
|
||||||
|
return (
|
||||||
|
<pre class="pre">
|
||||||
|
<code class="language-html">{value}</code>
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render (h) {
|
||||||
|
const example1 = {
|
||||||
|
'base': '<date-picker v-model="value1" lang="en" :not-before="new Date()"></date-picker>',
|
||||||
|
'range': '<date-picker v-model="value2" range lang="en"></date-picker>'
|
||||||
|
}
|
||||||
|
const example2 = {
|
||||||
|
'datetime': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value3"
|
||||||
|
lang="en"
|
||||||
|
type="datetime"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"></date-picker>`,
|
||||||
|
'datetime with time-picker-options': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value4"
|
||||||
|
lang="en"
|
||||||
|
type="datetime"
|
||||||
|
format="YYYY-MM-DD hh:mm:ss a"
|
||||||
|
:time-picker-options="{
|
||||||
|
start: '00:00',
|
||||||
|
step: '00:30',
|
||||||
|
end: '23:30'
|
||||||
|
}"></date-picker>`,
|
||||||
|
'datetime with minute-step': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value9"
|
||||||
|
lang="en"
|
||||||
|
type="datetime"
|
||||||
|
format="YYYY-MM-DD hh:mm:ss a"
|
||||||
|
:minute-step="10"
|
||||||
|
></date-picker>`,
|
||||||
|
'datetime range': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value5"
|
||||||
|
range
|
||||||
|
type="datetime"
|
||||||
|
lang="en"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"></date-picker>`
|
||||||
|
}
|
||||||
|
const example3 = {
|
||||||
|
'with confirm': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value6"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
lang="en"
|
||||||
|
confirm></date-picker>`,
|
||||||
|
'datetime with confirm': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value7"
|
||||||
|
type="datetime"
|
||||||
|
lang="en"
|
||||||
|
format="YYYY-MM-DD hh:mm:ss"
|
||||||
|
confirm></date-picker>`,
|
||||||
|
'range width confirm': `
|
||||||
|
<date-picker
|
||||||
|
v-model="value8"
|
||||||
|
range
|
||||||
|
lang="en"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
confirm></date-picker>`
|
||||||
|
}
|
||||||
|
const arr = [
|
||||||
|
{
|
||||||
|
exam: example1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
exam: example2,
|
||||||
|
tips: 'if you use the datetime, you should set the format to "YYYY-MM-DD HH:mm:ss" which default is "YYY-MM-DD'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
exam: example3,
|
||||||
|
tips: 'Recommend to use the confirm option when the type is "datetime" or "range" is true'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<div id="app">
|
||||||
|
{arr.map(obj => (
|
||||||
|
<div class="example">
|
||||||
|
{this.getSource(obj.exam)}
|
||||||
|
{
|
||||||
|
obj.tips
|
||||||
|
? <blockquote class="tips">{obj.tips}</blockquote>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
{this.getPre(obj.exam)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
|
|
||||||
new Vue({ // eslint-disable-line
|
|
||||||
el: '#app',
|
|
||||||
render: h => h(App)
|
|
||||||
})
|
|
||||||
Vendored
-1
File diff suppressed because one or more lines are too long
-11
@@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>vue2-datepicker</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script src="demo/build.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Generated
+8313
-1299
File diff suppressed because it is too large
Load Diff
+67
-28
@@ -1,12 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "vue2-datepicker",
|
"name": "vue2-datepicker",
|
||||||
"description": "A Datepicker Component For Vue2",
|
"description": "A Datepicker Component For Vue2",
|
||||||
"main": "dist/build.js",
|
"main": "lib/index.js",
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
"version": "1.9.8",
|
"version": "1.9.8",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
|
"dev": "cross-env NODE_ENV=development webpack-dev-server --hot --open --config build/webpack.dev.config.js",
|
||||||
"demo": "cross-env NODE_ENV=production webpack --progress --hide-modules --config webpack.demo.config.js",
|
"demo": "cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.demo.config.js",
|
||||||
"deploy": "cross-env NODE_ENV=production webpack --progress --hide-modules --config webpack.deploy.config.js"
|
"deploy": "cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.deploy.config.js",
|
||||||
|
"test:push": "jest --coverage --coverageReporters=text-lcov | coveralls",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
|
||||||
|
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
|
||||||
|
},
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@/(.*)$": "<rootDir>/src/$1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -23,34 +42,54 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/mengxiong10/vue2-datepicker#readme",
|
"homepage": "https://github.com/mengxiong10/vue2-datepicker#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^2.2.1"
|
"fecha": "^2.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^7.1.6",
|
"@vue/test-utils": "^1.0.0-beta.18",
|
||||||
"babel-core": "^6.0.0",
|
"autoprefixer": "^7.2.6",
|
||||||
"babel-eslint": "^8.0.2",
|
"babel-core": "^6.26.3",
|
||||||
"babel-loader": "^7.0.0",
|
"babel-eslint": "^8.2.3",
|
||||||
"babel-preset-env": "^1.6.0",
|
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||||
|
"babel-jest": "^23.0.1",
|
||||||
|
"babel-loader": "^7.1.4",
|
||||||
|
"babel-plugin-jsx-v-model": "^2.0.3",
|
||||||
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
|
"babel-plugin-transform-vue-jsx": "^3.7.0",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
"babel-preset-stage-3": "^6.24.1",
|
"babel-preset-stage-3": "^6.24.1",
|
||||||
"cross-env": "^5.0.0",
|
"coveralls": "^3.0.1",
|
||||||
|
"cross-env": "^5.1.6",
|
||||||
"css-loader": "^0.25.0",
|
"css-loader": "^0.25.0",
|
||||||
"eslint": "^4.12.0",
|
"cz-conventional-changelog": "^2.1.0",
|
||||||
"eslint-config-standard": "^10.2.1",
|
"eslint": "^4.19.1",
|
||||||
|
"eslint-config-standard": "^11.0.0",
|
||||||
"eslint-friendly-formatter": "^3.0.0",
|
"eslint-friendly-formatter": "^3.0.0",
|
||||||
"eslint-loader": "^1.9.0",
|
"eslint-loader": "^2.0.0",
|
||||||
"eslint-plugin-html": "^4.0.1",
|
"eslint-plugin-import": "^2.12.0",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint-plugin-node": "^6.0.1",
|
||||||
"eslint-plugin-node": "^5.2.1",
|
"eslint-plugin-promise": "^3.8.0",
|
||||||
"eslint-plugin-promise": "^3.6.0",
|
"eslint-plugin-standard": "^3.1.0",
|
||||||
"eslint-plugin-standard": "^3.0.1",
|
"eslint-plugin-vue": "^4.5.0",
|
||||||
"file-loader": "^0.9.0",
|
"file-loader": "^1.1.11",
|
||||||
"node-sass": "^4.7.2",
|
"highlight.js": "^9.12.0",
|
||||||
"postcss-loader": "^2.0.9",
|
"jest": "^23.0.1",
|
||||||
"sass-loader": "^6.0.6",
|
"mini-css-extract-plugin": "^0.4.0",
|
||||||
"vue-loader": "^13.0.5",
|
"node-sass": "^4.9.0",
|
||||||
"vue-template-compiler": "^2.2.1",
|
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||||
"webpack": "^2.2.0",
|
"postcss-loader": "^2.1.5",
|
||||||
"webpack-dev-server": "^2.2.0",
|
"sass-loader": "^6.0.7",
|
||||||
"webpack-merge": "^4.1.1"
|
"vue": "^2.5.16",
|
||||||
|
"vue-jest": "^2.6.0",
|
||||||
|
"vue-loader": "^15.2.1",
|
||||||
|
"vue-template-compiler": "^2.5.16",
|
||||||
|
"webpack": "^4.9.1",
|
||||||
|
"webpack-cli": "^2.1.4",
|
||||||
|
"webpack-dev-server": "^3.1.4",
|
||||||
|
"webpack-merge": "^4.1.2"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "./node_modules/cz-conventional-changelog"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mx-calendar">
|
||||||
|
<div class="mx-calendar-header">
|
||||||
|
<a
|
||||||
|
v-show="panel !== 'TIME'"
|
||||||
|
class="mx-icon-last-year"
|
||||||
|
@click="handleIconYear(-1)">«</a>
|
||||||
|
<a
|
||||||
|
v-show="panel === 'DATE'"
|
||||||
|
class="mx-icon-last-month"
|
||||||
|
@click="handleIconMonth(-1)">‹</a>
|
||||||
|
<a
|
||||||
|
v-show="panel !== 'TIME'"
|
||||||
|
class="mx-icon-next-year"
|
||||||
|
@click="handleIconYear(1)">»</a>
|
||||||
|
<a
|
||||||
|
v-show="panel === 'DATE'"
|
||||||
|
class="mx-icon-next-month"
|
||||||
|
@click="handleIconMonth(1)">›</a>
|
||||||
|
<a
|
||||||
|
v-show="panel !== 'TIME'"
|
||||||
|
class="mx-current-month"
|
||||||
|
@click="handleBtnMonth">{{months[calendarMonth]}}</a>
|
||||||
|
<a
|
||||||
|
v-show="panel !== 'TIME'"
|
||||||
|
class="mx-current-year"
|
||||||
|
@click="handleBtnYear">{{calendarYear}}</a>
|
||||||
|
<a
|
||||||
|
v-show="panel === 'TIME'"
|
||||||
|
class="mx-time-header"
|
||||||
|
@click="showPanelDate">{{timeHeader}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="mx-calendar-content">
|
||||||
|
<panel-date
|
||||||
|
v-show="panel === 'DATE'"
|
||||||
|
:value="value"
|
||||||
|
:calendar-month="calendarMonth"
|
||||||
|
:calendar-year="calendarYear"
|
||||||
|
:start-at="startAt"
|
||||||
|
:end-at="endAt"
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:disabled-date="isDisabledDate"
|
||||||
|
@select="selectDate"/>
|
||||||
|
<panel-year
|
||||||
|
v-show="panel === 'YEAR'"
|
||||||
|
:value="value"
|
||||||
|
:first-year="firstYear"
|
||||||
|
@select="selectYear" />
|
||||||
|
<panel-month
|
||||||
|
v-show="panel === 'MONTH'"
|
||||||
|
:value="value"
|
||||||
|
:calendar-year="calendarYear"
|
||||||
|
@select="selectMonth" />
|
||||||
|
<panel-time
|
||||||
|
v-show="panel === 'TIME'"
|
||||||
|
:minute-step="minuteStep"
|
||||||
|
:time-picker-options="timePickerOptions"
|
||||||
|
:value="value"
|
||||||
|
:disabled-time="isDisabledTime"
|
||||||
|
@select="selectTime" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isValidDate, isDateObejct } from '@/utils/index'
|
||||||
|
import { t } from '@/locale/index'
|
||||||
|
import scrollIntoView from '@/utils/scroll-into-view'
|
||||||
|
import PanelDate from '@/panel/date'
|
||||||
|
import PanelYear from '@/panel/year'
|
||||||
|
import PanelMonth from '@/panel/month'
|
||||||
|
import PanelTime from '@/panel/time'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CalendarPanel',
|
||||||
|
components: { PanelDate, PanelYear, PanelMonth, PanelTime },
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
default: null,
|
||||||
|
validator: function (val) {
|
||||||
|
return val === null || isValidDate(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startAt: null,
|
||||||
|
endAt: null,
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
// below user set
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'date' // ['date', 'datetime']
|
||||||
|
},
|
||||||
|
firstDayOfWeek: {
|
||||||
|
default: 7,
|
||||||
|
type: Number,
|
||||||
|
validator: val => val >= 1 && val <= 7
|
||||||
|
},
|
||||||
|
notBefore: {
|
||||||
|
default: null,
|
||||||
|
validator: function (val) {
|
||||||
|
return !val || isValidDate(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notAfter: {
|
||||||
|
default: null,
|
||||||
|
validator: function (val) {
|
||||||
|
return !val || isValidDate(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disabledDays: {
|
||||||
|
type: [Array, Function],
|
||||||
|
default: function () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minuteStep: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
validator: val => val >= 0 && val <= 60
|
||||||
|
},
|
||||||
|
timePickerOptions: {
|
||||||
|
type: [Object, Function],
|
||||||
|
default () {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
const now = new Date()
|
||||||
|
const calendarYear = now.getFullYear()
|
||||||
|
const calendarMonth = now.getMonth()
|
||||||
|
const firstYear = Math.floor(calendarYear / 10) * 10
|
||||||
|
const months = t('months')
|
||||||
|
return {
|
||||||
|
panel: 'DATE',
|
||||||
|
dates: [],
|
||||||
|
months,
|
||||||
|
calendarMonth,
|
||||||
|
calendarYear,
|
||||||
|
firstYear
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
now: {
|
||||||
|
get () {
|
||||||
|
return new Date(this.calendarYear, this.calendarMonth).getTime()
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
const now = new Date(val)
|
||||||
|
this.calendarYear = now.getFullYear()
|
||||||
|
this.calendarMonth = now.getMonth()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeHeader () {
|
||||||
|
return this.value && new Date(this.value).toLocaleDateString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler: 'updateNow'
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
immediate: true,
|
||||||
|
handler: 'init'
|
||||||
|
},
|
||||||
|
panel: {
|
||||||
|
immediate: true,
|
||||||
|
handler: 'handelPanelChange'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handelPanelChange (panel) {
|
||||||
|
if (panel === 'YEAR') {
|
||||||
|
this.firstYear = Math.floor(this.calendarYear / 10) * 10
|
||||||
|
} else if (panel === 'TIME') {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
[...this.$el.querySelectorAll('.mx-panel-time .mx-time-list')].forEach(el => {
|
||||||
|
scrollIntoView(el, el.querySelector('.actived'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
init () {
|
||||||
|
this.panel = 'DATE'
|
||||||
|
this.updateNow(this.value)
|
||||||
|
},
|
||||||
|
// 根据value更新日历
|
||||||
|
updateNow (value) {
|
||||||
|
this.now = value ? new Date(value) : new Date()
|
||||||
|
},
|
||||||
|
isDisabledTime (date, startAt, endAt) {
|
||||||
|
const time = new Date(date).getTime()
|
||||||
|
const notBefore = this.notBefore && (time < new Date(this.notBefore))
|
||||||
|
const notAfter = this.notAfter && (time > new Date(this.notAfter))
|
||||||
|
startAt = startAt === undefined ? this.startAt : startAt
|
||||||
|
startAt = startAt && (time < new Date(startAt))
|
||||||
|
endAt = endAt === undefined ? this.endAt : endAt
|
||||||
|
endAt = endAt && (time > new Date(endAt))
|
||||||
|
return notBefore || notAfter || startAt || endAt
|
||||||
|
},
|
||||||
|
isDisabledDate (date, startAt, endAt) {
|
||||||
|
const time = new Date(date).getTime()
|
||||||
|
const notBefore = this.notBefore && (time < new Date(this.notBefore).setHours(0, 0, 0, 0))
|
||||||
|
const notAfter = this.notAfter && (time > new Date(this.notAfter).setHours(0, 0, 0, 0))
|
||||||
|
startAt = startAt === undefined ? this.startAt : startAt
|
||||||
|
startAt = startAt && (time < new Date(startAt).setHours(0, 0, 0, 0))
|
||||||
|
endAt = endAt === undefined ? this.endAt : endAt
|
||||||
|
endAt = endAt && (time > new Date(endAt).setHours(0, 0, 0, 0))
|
||||||
|
let disabledDays = false
|
||||||
|
if (Array.isArray(this.disabledDays)) {
|
||||||
|
disabledDays = this.disabledDays.some(v => new Date(v).setHours(0, 0, 0, 0) === time)
|
||||||
|
} else if (typeof this.disabledDays === 'function') {
|
||||||
|
disabledDays = this.disabledDays(new Date(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
return notBefore || notAfter || disabledDays || startAt || endAt
|
||||||
|
},
|
||||||
|
selectDate (date) {
|
||||||
|
if (this.type === 'datetime') {
|
||||||
|
let time = new Date(date)
|
||||||
|
if (isDateObejct(this.value)) {
|
||||||
|
time.setHours(
|
||||||
|
this.value.getHours(),
|
||||||
|
this.value.getMinutes(),
|
||||||
|
this.value.getSeconds()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (this.isDisabledTime(time)) {
|
||||||
|
time.setHours(0, 0, 0, 0)
|
||||||
|
if (this.notBefore && time.getTime() < new Date(this.notBefore).getTime()) {
|
||||||
|
time = new Date(this.notBefore)
|
||||||
|
}
|
||||||
|
if (this.startAt && time.getTime() < new Date(this.startAt).getTime()) {
|
||||||
|
time = new Date(this.startAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('select-time', time)
|
||||||
|
this.panel = 'TIME'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('select-date', date)
|
||||||
|
},
|
||||||
|
selectYear (year) {
|
||||||
|
this.changeCalendarYear(year)
|
||||||
|
this.showPanelMonth()
|
||||||
|
},
|
||||||
|
selectMonth (month) {
|
||||||
|
this.changeCalendarMonth(month)
|
||||||
|
this.showPanelDate()
|
||||||
|
},
|
||||||
|
selectTime (time) {
|
||||||
|
this.$emit('select-time', time)
|
||||||
|
},
|
||||||
|
changeCalendarYear (year) {
|
||||||
|
this.now = new Date(year, this.calendarMonth)
|
||||||
|
},
|
||||||
|
changeCalendarMonth (month) {
|
||||||
|
this.now = new Date(this.calendarYear, month)
|
||||||
|
},
|
||||||
|
handleIconMonth (flag) {
|
||||||
|
this.changeCalendarMonth(this.calendarMonth + flag)
|
||||||
|
},
|
||||||
|
handleIconYear (flag) {
|
||||||
|
if (this.panel === 'YEAR') {
|
||||||
|
this.changePanelYears(flag)
|
||||||
|
} else {
|
||||||
|
this.changeCalendarYear(this.calendarYear + flag)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleBtnYear () {
|
||||||
|
if (this.panel === 'YEAR') {
|
||||||
|
this.showPanelDate()
|
||||||
|
} else {
|
||||||
|
this.showPanelYear()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleBtnMonth () {
|
||||||
|
if (this.panel === 'MONTH') {
|
||||||
|
this.showPanelDate()
|
||||||
|
} else {
|
||||||
|
this.showPanelMonth()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changePanelYears (flag) {
|
||||||
|
this.firstYear = this.firstYear + flag * 10
|
||||||
|
},
|
||||||
|
showPanelDate () {
|
||||||
|
this.panel = 'DATE'
|
||||||
|
},
|
||||||
|
showPanelYear () {
|
||||||
|
this.panel = 'YEAR'
|
||||||
|
},
|
||||||
|
showPanelMonth () {
|
||||||
|
this.panel = 'MONTH'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
bind (el, binding, vnode) {
|
||||||
|
el['@clickoutside'] = e => {
|
||||||
|
if (
|
||||||
|
!el.contains(e.target) &&
|
||||||
|
binding.expression &&
|
||||||
|
vnode.context[binding.expression]
|
||||||
|
) {
|
||||||
|
binding.value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('click', el['@clickoutside'], true)
|
||||||
|
},
|
||||||
|
unbind (el) {
|
||||||
|
document.removeEventListener('click', el['@clickoutside'], true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import DatePicker from './datepicker/index.vue'
|
import DatePicker from './index.vue'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
DatePicker.install = function (Vue) {
|
DatePicker.install = function (Vue) {
|
||||||
Vue.component(DatePicker.name, DatePicker)
|
Vue.component(DatePicker.name, DatePicker)
|
||||||
+319
@@ -0,0 +1,319 @@
|
|||||||
|
$default-color: #73879c;
|
||||||
|
$primary-color: #1284e7;
|
||||||
|
|
||||||
|
.mx-datepicker {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 210px;
|
||||||
|
color: $default-color;
|
||||||
|
font: 14px/1.5 'Helvetica Neue', Helvetica, Arial, 'Microsoft Yahei', sans-serif;
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mx-datepicker-range {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-datepicker-popup {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
.mx-clear-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.mx-clear-wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-input {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 34px;
|
||||||
|
padding: 6px 30px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #555;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
&:disabled,
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-input-append {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-input-icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-style: normal;
|
||||||
|
color: #555;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-calendar-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #555;
|
||||||
|
stroke-width: 8px;
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-clear-icon {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: '\2716';
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-range-wrapper {
|
||||||
|
width: 248px * 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-shortcuts-wrapper {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 12px;
|
||||||
|
line-height: 34px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
.mx-shortcuts {
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: mix(#fff, $primary-color, 20%);
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '|';
|
||||||
|
margin: 0 10px;
|
||||||
|
color: #48576a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-datepicker-footer {
|
||||||
|
padding: 4px;
|
||||||
|
clear: both;
|
||||||
|
text-align: right;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.mx-datepicker-btn {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 7px 15px;
|
||||||
|
margin: 0 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.mx-datepicker-btn-confirm {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
color: #73879c;
|
||||||
|
&:hover {
|
||||||
|
color: #1284e7;
|
||||||
|
border-color: #1284e7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日历组件 */
|
||||||
|
.mx-calendar {
|
||||||
|
float: left;
|
||||||
|
color: $default-color;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font: 14px/1.5 Helvetica Neue,Helvetica,Arial,Microsoft Yahei,sans-serif;
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-calendar-header {
|
||||||
|
padding: 0 4px;
|
||||||
|
height: 34px;
|
||||||
|
line-height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
> a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: mix(#fff, $primary-color, 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@at-root {
|
||||||
|
.mx-icon-last-month,
|
||||||
|
.mx-icon-next-month {
|
||||||
|
padding: 0 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.mx-icon-last-month {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.mx-icon-next-month {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.mx-icon-last-year {
|
||||||
|
@extend .mx-icon-last-month
|
||||||
|
}
|
||||||
|
.mx-icon-next-year {
|
||||||
|
@extend .mx-icon-next-month
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-calendar-content {
|
||||||
|
width: 32px * 7;
|
||||||
|
height: 32px * 7;
|
||||||
|
.cell {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #eaf8fe;
|
||||||
|
}
|
||||||
|
&.actived {
|
||||||
|
color: #fff;
|
||||||
|
background-color: $primary-color;
|
||||||
|
}
|
||||||
|
&.inrange {
|
||||||
|
background-color: #eaf8fe;
|
||||||
|
}
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #ccc;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-panel {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-panel-date {
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
td, th {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
&.today {
|
||||||
|
color: mix(#fff, $primary-color, 10%);
|
||||||
|
}
|
||||||
|
&.last-month,
|
||||||
|
&.next-month {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-panel-year {
|
||||||
|
padding: 7px 0;
|
||||||
|
.cell {
|
||||||
|
display: inline-block;
|
||||||
|
width: 40%;
|
||||||
|
margin: 1px 5%;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-panel-month {
|
||||||
|
.cell {
|
||||||
|
display: inline-block;
|
||||||
|
width: 30%;
|
||||||
|
line-height: 40px;
|
||||||
|
margin: 8px 1.5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-time-list {
|
||||||
|
position: relative; // 定位 offsetParent
|
||||||
|
float: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-top: 1px solid rgba(0,0,0,.05);
|
||||||
|
border-left: 1px solid rgba(0,0,0,.05);
|
||||||
|
overflow-y: auto;
|
||||||
|
.mx-time-picker-item {
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
&:first-child {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
/* 滚动条滑块 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
+409
@@ -0,0 +1,409 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="mx-datepicker"
|
||||||
|
:class="{
|
||||||
|
'mx-datepicker-range': range,
|
||||||
|
'disabled': disabled
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
'width': computedWidth
|
||||||
|
}"
|
||||||
|
v-clickoutside="closePopup">
|
||||||
|
<div class="mx-input-wrapper"
|
||||||
|
@click="showPopup">
|
||||||
|
<input
|
||||||
|
:class="inputClass"
|
||||||
|
ref="input"
|
||||||
|
type="text"
|
||||||
|
:name="inputName"
|
||||||
|
:disabled="disabled"
|
||||||
|
:readonly="!editable"
|
||||||
|
:value="text"
|
||||||
|
:placeholder="innerPlaceholder"
|
||||||
|
@input="handleInput"
|
||||||
|
@change="handleChange">
|
||||||
|
<span class="mx-input-append">
|
||||||
|
<slot name="calendar-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 200 200" class="mx-calendar-icon">
|
||||||
|
<rect x="13" y="29" rx="14" ry="14" width="174" height="158" fill="transparent" />
|
||||||
|
<line x1="46" x2="46" y1="8" y2="50" />
|
||||||
|
<line x1="154" x2="154" y1="8" y2="50" />
|
||||||
|
<line x1="13" x2="187" y1="70" y2="70" />
|
||||||
|
<text x="50%" y="135" font-size="90" stroke-width="1" text-anchor="middle" dominant-baseline="middle">{{new Date().getDate()}}</text>
|
||||||
|
</svg>
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="showClearIcon"
|
||||||
|
class="mx-input-append mx-clear-wrapper"
|
||||||
|
@click.stop="clearDate">
|
||||||
|
<slot name="mx-clear-icon">
|
||||||
|
<i class="mx-input-icon mx-clear-icon"></i>
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mx-datepicker-popup"
|
||||||
|
:style="position"
|
||||||
|
v-show="popupVisible"
|
||||||
|
ref="calendar">
|
||||||
|
<slot name="header">
|
||||||
|
<div class="mx-shortcuts-wrapper"
|
||||||
|
v-if="range && innnerShortcuts.length">
|
||||||
|
<span
|
||||||
|
class="mx-shortcuts"
|
||||||
|
v-for="(range, index) in innnerShortcuts"
|
||||||
|
:key="index"
|
||||||
|
@click="selectRange(range)">{{range.text}}</span>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
<calendar-panel
|
||||||
|
v-if="!range"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:value="currentValue"
|
||||||
|
:visible="popupVisible"
|
||||||
|
@select-date="selectDate"
|
||||||
|
@select-time="selectTime"></calendar-panel>
|
||||||
|
<div class="mx-range-wrapper"
|
||||||
|
v-else>
|
||||||
|
<calendar-panel
|
||||||
|
style="box-shadow:1px 0 rgba(0, 0, 0, .1)"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:value="currentValue[0]"
|
||||||
|
:end-at="currentValue[1]"
|
||||||
|
:start-at="null"
|
||||||
|
:visible="popupVisible"
|
||||||
|
@select-date="selectStartDate"
|
||||||
|
@select-time="selectStartTime"></calendar-panel>
|
||||||
|
<calendar-panel
|
||||||
|
v-bind="$attrs"
|
||||||
|
:value="currentValue[1]"
|
||||||
|
:start-at="currentValue[0]"
|
||||||
|
:end-at="null"
|
||||||
|
:visible="popupVisible"
|
||||||
|
@select-date="selectEndDate"
|
||||||
|
@select-time="selectEndTime"></calendar-panel>
|
||||||
|
</div>
|
||||||
|
<slot name="footer">
|
||||||
|
<div class="mx-datepicker-footer"
|
||||||
|
v-if="confirm">
|
||||||
|
<button type="button"
|
||||||
|
class="mx-datepicker-btn mx-datepicker-btn-confirm"
|
||||||
|
@click="confirmDate">{{ confirmText }}</button>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import fecha from 'fecha'
|
||||||
|
import clickoutside from '@/directives/clickoutside'
|
||||||
|
import { isValidDate, isValidRange, isDateObejct } from '@/utils/index'
|
||||||
|
import { use, t } from '@/locale/index'
|
||||||
|
import CalendarPanel from './calendar.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DatePicker',
|
||||||
|
components: { CalendarPanel },
|
||||||
|
directives: {
|
||||||
|
clickoutside
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: null,
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
lang: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: 'zh'
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
type: String,
|
||||||
|
default: 'YYYY-MM-DD'
|
||||||
|
},
|
||||||
|
range: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rangeSeparator: {
|
||||||
|
type: String,
|
||||||
|
default: '~'
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
confirmText: {
|
||||||
|
type: String,
|
||||||
|
default: 'OK'
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
shortcuts: {
|
||||||
|
type: [Boolean, Array],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
inputName: {
|
||||||
|
type: String,
|
||||||
|
default: 'date'
|
||||||
|
},
|
||||||
|
inputClass: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: 'mx-input'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
currentValue: this.range ? [null, null] : null,
|
||||||
|
userInput: null,
|
||||||
|
popupVisible: false,
|
||||||
|
position: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler: 'handleValueChange'
|
||||||
|
},
|
||||||
|
popupVisible (val) {
|
||||||
|
if (val) {
|
||||||
|
this.initCalendar()
|
||||||
|
} else {
|
||||||
|
this.userInput = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
innerPlaceholder () {
|
||||||
|
if (typeof this.placeholder === 'string') {
|
||||||
|
return this.placeholder
|
||||||
|
}
|
||||||
|
return this.range ? t('placeholder.dateRange') : t('placeholder.date')
|
||||||
|
},
|
||||||
|
text () {
|
||||||
|
if (this.userInput !== null) {
|
||||||
|
return this.userInput
|
||||||
|
}
|
||||||
|
if (!this.range) {
|
||||||
|
return isValidDate(this.value) ? this.stringify(this.value) : ''
|
||||||
|
}
|
||||||
|
return isValidRange(this.value)
|
||||||
|
? `${this.stringify(this.value[0])} ${this.rangeSeparator} ${this.stringify(this.value[1])}`
|
||||||
|
: ''
|
||||||
|
},
|
||||||
|
computedWidth () {
|
||||||
|
if (typeof this.width === 'number' || (typeof this.width === 'string' && /^\d+$/.test(this.width))) {
|
||||||
|
return this.width + 'px'
|
||||||
|
}
|
||||||
|
return this.width
|
||||||
|
},
|
||||||
|
showClearIcon () {
|
||||||
|
return !this.disabled && this.clearable && (this.range ? isValidRange(this.value) : isValidDate(this.value))
|
||||||
|
},
|
||||||
|
innnerShortcuts () {
|
||||||
|
if (Array.isArray(this.shortcuts)) {
|
||||||
|
return this.shortcuts
|
||||||
|
}
|
||||||
|
if (this.shortcuts === false) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const pickers = t('pickers')
|
||||||
|
const arr = [
|
||||||
|
{
|
||||||
|
text: pickers[0],
|
||||||
|
start: new Date(),
|
||||||
|
end: new Date(Date.now() + 3600 * 1000 * 24 * 7)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: pickers[1],
|
||||||
|
start: new Date(),
|
||||||
|
end: new Date(Date.now() + 3600 * 1000 * 24 * 30)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: pickers[2],
|
||||||
|
start: new Date(Date.now() - 3600 * 1000 * 24 * 7),
|
||||||
|
end: new Date()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: pickers[3],
|
||||||
|
start: new Date(Date.now() - 3600 * 1000 * 24 * 30),
|
||||||
|
end: new Date()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
use(this.lang)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initCalendar () {
|
||||||
|
this.handleValueChange(this.value)
|
||||||
|
this.displayPopup()
|
||||||
|
},
|
||||||
|
stringify (date, format) {
|
||||||
|
format = format || this.format
|
||||||
|
return fecha.format(date, format)
|
||||||
|
},
|
||||||
|
parseDate (value, format) {
|
||||||
|
try {
|
||||||
|
format = format || this.format
|
||||||
|
return fecha.parse(value, format)
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dateEqual (a, b) {
|
||||||
|
return isDateObejct(a) && isDateObejct(b) && a.getTime() === b.getTime()
|
||||||
|
},
|
||||||
|
rangeEqual (a, b) {
|
||||||
|
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => this.dateEqual(item, b[index]))
|
||||||
|
},
|
||||||
|
selectRange (range) {
|
||||||
|
this.currentValue = [ new Date(range.start), new Date(range.end) ]
|
||||||
|
this.updateDate(true)
|
||||||
|
},
|
||||||
|
clearDate () {
|
||||||
|
const date = this.range ? [null, null] : null
|
||||||
|
this.currentValue = date
|
||||||
|
this.updateDate(true)
|
||||||
|
},
|
||||||
|
confirmDate () {
|
||||||
|
const valid = this.range ? isValidRange(this.currentValue) : isValidDate(this.currentValue)
|
||||||
|
if (valid) {
|
||||||
|
this.updateDate(true)
|
||||||
|
}
|
||||||
|
this.$emit('confirm', this.currentValue)
|
||||||
|
this.closePopup()
|
||||||
|
},
|
||||||
|
updateDate (confirm = false) {
|
||||||
|
if ((this.confirm && !confirm) || this.disabled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const equal = this.range ? this.rangeEqual(this.value, this.currentValue) : this.dateEqual(this.value, this.currentValue)
|
||||||
|
if (equal) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
this.$emit('input', this.currentValue)
|
||||||
|
this.$emit('change', this.currentValue)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
handleValueChange (value) {
|
||||||
|
if (!this.range) {
|
||||||
|
this.currentValue = isValidDate(value) ? new Date(value) : null
|
||||||
|
} else {
|
||||||
|
this.currentValue = isValidRange(value) ? [new Date(value[0]), new Date(value[1])] : [null, null]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectDate (date) {
|
||||||
|
this.currentValue = date
|
||||||
|
this.updateDate() && this.closePopup()
|
||||||
|
},
|
||||||
|
selectStartDate (date) {
|
||||||
|
this.$set(this.currentValue, 0, date)
|
||||||
|
if (this.currentValue[1]) {
|
||||||
|
this.updateDate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectEndDate (date) {
|
||||||
|
this.$set(this.currentValue, 1, date)
|
||||||
|
if (this.currentValue[0]) {
|
||||||
|
this.updateDate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectTime (time) {
|
||||||
|
this.currentValue = time
|
||||||
|
this.updateDate()
|
||||||
|
},
|
||||||
|
selectStartTime (time) {
|
||||||
|
this.selectStartDate(time)
|
||||||
|
},
|
||||||
|
selectEndTime (time) {
|
||||||
|
this.selectEndDate(time)
|
||||||
|
},
|
||||||
|
showPopup () {
|
||||||
|
if (this.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.popupVisible = true
|
||||||
|
},
|
||||||
|
closePopup () {
|
||||||
|
this.popupVisible = false
|
||||||
|
},
|
||||||
|
displayPopup () {
|
||||||
|
const dw = document.documentElement.clientWidth
|
||||||
|
const dh = document.documentElement.clientHeight
|
||||||
|
const InputRect = this.$el.getBoundingClientRect()
|
||||||
|
const PopupRect = this.$refs.calendar.getBoundingClientRect()
|
||||||
|
this.position = {}
|
||||||
|
if (
|
||||||
|
dw - InputRect.left < PopupRect.width &&
|
||||||
|
InputRect.right < PopupRect.width
|
||||||
|
) {
|
||||||
|
this.position.left = 1 - InputRect.left + 'px'
|
||||||
|
} else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
|
||||||
|
this.position.left = 0
|
||||||
|
} else {
|
||||||
|
this.position.right = 0
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
InputRect.top <= PopupRect.height + 1 &&
|
||||||
|
dh - InputRect.bottom <= PopupRect.height + 1
|
||||||
|
) {
|
||||||
|
this.position.top = dh - InputRect.top - PopupRect.height - 1 + 'px'
|
||||||
|
} else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
|
||||||
|
this.position.top = '100%'
|
||||||
|
} else {
|
||||||
|
this.position.bottom = '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleInput (event) {
|
||||||
|
this.userInput = event.target.value
|
||||||
|
},
|
||||||
|
handleChange (event) {
|
||||||
|
const value = event.target.value
|
||||||
|
if (this.editable && this.userInput !== null) {
|
||||||
|
const calendar = this.$children[0]
|
||||||
|
const checkDate = calendar.type === 'date' ? calendar.isDisabledDate : calendar.isDisabledTime
|
||||||
|
if (this.range) {
|
||||||
|
const range = value.split(` ${this.rangeSeparator} `)
|
||||||
|
if (range.length === 2) {
|
||||||
|
const start = this.parseDate(range[0], this.format)
|
||||||
|
const end = this.parseDate(range[1], this.format)
|
||||||
|
if (start && end && !checkDate(start, null, end) && !checkDate(end, start, null)) {
|
||||||
|
this.currentValue = [ start, end ]
|
||||||
|
this.updateDate(true)
|
||||||
|
this.closePopup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const date = this.parseDate(value, this.format)
|
||||||
|
if (date && !checkDate(date, null, null)) {
|
||||||
|
this.currentValue = date
|
||||||
|
this.updateDate(true)
|
||||||
|
this.closePopup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('input-error', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import Languages from './languages'
|
||||||
|
import { isPlainObject } from '@/utils/index'
|
||||||
|
|
||||||
|
let lang = 'zh'
|
||||||
|
|
||||||
|
export function use (target) {
|
||||||
|
if (isPlainObject(target)) {
|
||||||
|
lang = { ...Languages.en, ...target }
|
||||||
|
} else {
|
||||||
|
lang = Languages[target] || Languages.en
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function t (path) {
|
||||||
|
const arr = path.split('.')
|
||||||
|
let current = lang
|
||||||
|
let value
|
||||||
|
for (let i = 0, len = arr.length; i < len; i++) {
|
||||||
|
const prop = arr[i]
|
||||||
|
value = current[prop]
|
||||||
|
if (i === len - 1) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (!value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
current = value
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { t } from '@/locale/index'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'panelDate',
|
||||||
|
props: {
|
||||||
|
value: null,
|
||||||
|
startAt: null,
|
||||||
|
endAt: null,
|
||||||
|
calendarMonth: {
|
||||||
|
default: new Date().getMonth()
|
||||||
|
},
|
||||||
|
calendarYear: {
|
||||||
|
default: new Date().getFullYear()
|
||||||
|
},
|
||||||
|
firstDayOfWeek: {
|
||||||
|
default: 7,
|
||||||
|
type: Number,
|
||||||
|
validator: val => val >= 1 && val <= 7
|
||||||
|
},
|
||||||
|
disabledDate: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectDate ({ year, month, day }) {
|
||||||
|
const date = new Date(year, month, day)
|
||||||
|
if (this.disabledDate(date)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('select', date)
|
||||||
|
},
|
||||||
|
getDays (firstDayOfWeek) {
|
||||||
|
const days = t('days')
|
||||||
|
const firstday = parseInt(firstDayOfWeek, 10)
|
||||||
|
return days.concat(days).slice(firstday, firstday + 7)
|
||||||
|
},
|
||||||
|
getDates (year, month, firstDayOfWeek) {
|
||||||
|
const arr = []
|
||||||
|
const time = new Date(year, month)
|
||||||
|
|
||||||
|
time.setDate(0) // 把时间切换到上个月最后一天
|
||||||
|
const lastMonthLength = (time.getDay() + 7 - firstDayOfWeek) % 7 + 1 // time.getDay() 0是星期天, 1是星期一 ...
|
||||||
|
const lastMonthfirst = time.getDate() - (lastMonthLength - 1)
|
||||||
|
for (let i = 0; i < lastMonthLength; i++) {
|
||||||
|
arr.push({ year, month: month - 1, day: lastMonthfirst + i })
|
||||||
|
}
|
||||||
|
|
||||||
|
time.setMonth(time.getMonth() + 2, 0) // 切换到这个月最后一天
|
||||||
|
const curMonthLength = time.getDate()
|
||||||
|
for (let i = 0; i < curMonthLength; i++) {
|
||||||
|
arr.push({ year, month, day: 1 + i })
|
||||||
|
}
|
||||||
|
|
||||||
|
time.setMonth(time.getMonth() + 1, 1) // 切换到下个月第一天
|
||||||
|
const nextMonthLength = 42 - (lastMonthLength + curMonthLength)
|
||||||
|
for (let i = 0; i < nextMonthLength; i++) {
|
||||||
|
arr.push({ year, month: month + 1, day: 1 + i })
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr
|
||||||
|
},
|
||||||
|
getCellClasses ({ year, month, day }) {
|
||||||
|
const classes = []
|
||||||
|
const cellTime = new Date(year, month, day).getTime()
|
||||||
|
const today = new Date().setHours(0, 0, 0, 0)
|
||||||
|
const curTime = this.value && new Date(this.value).setHours(0, 0, 0, 0)
|
||||||
|
const startTime = this.startAt && new Date(this.startAt).setHours(0, 0, 0, 0)
|
||||||
|
const endTime = this.endAt && new Date(this.endAt).setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
if (month < this.calendarMonth) {
|
||||||
|
classes.push('last-month')
|
||||||
|
} else if (month > this.calendarMonth) {
|
||||||
|
classes.push('next-month')
|
||||||
|
} else {
|
||||||
|
classes.push('cur-month')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cellTime === today) {
|
||||||
|
classes.push('today')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.disabledDate(cellTime)) {
|
||||||
|
classes.push('disabled')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curTime) {
|
||||||
|
if (cellTime === curTime) {
|
||||||
|
classes.push('actived')
|
||||||
|
} else if (startTime && cellTime <= curTime) {
|
||||||
|
classes.push('inrange')
|
||||||
|
} else if (endTime && cellTime >= curTime) {
|
||||||
|
classes.push('inrange')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes
|
||||||
|
},
|
||||||
|
getCellTitle ({ year, month, day }) {
|
||||||
|
return new Date(year, month, day).toLocaleDateString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render (h) {
|
||||||
|
const ths = this.getDays(this.firstDayOfWeek).map(day => {
|
||||||
|
return <th>{day}</th>
|
||||||
|
})
|
||||||
|
|
||||||
|
const dates = this.getDates(this.calendarYear, this.calendarMonth, this.firstDayOfWeek)
|
||||||
|
const tbody = Array.apply(null, { length: 6 }).map((week, i) => {
|
||||||
|
const tds = dates.slice(7 * i, 7 * i + 7).map(date => {
|
||||||
|
const attrs = {
|
||||||
|
class: this.getCellClasses(date)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
class="cell"
|
||||||
|
{...attrs}
|
||||||
|
title={this.getCellTitle(date)}
|
||||||
|
onClick={this.selectDate.bind(this, date)}>
|
||||||
|
{date.day}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return <tr>{tds}</tr>
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table class="mx-panel mx-panel-date">
|
||||||
|
<thead>
|
||||||
|
<tr>{ths}</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{tbody}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { t } from '@/locale/index'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'panelMonth',
|
||||||
|
props: {
|
||||||
|
value: null,
|
||||||
|
calendarYear: {
|
||||||
|
default: new Date().getFullYear()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectMonth (month) {
|
||||||
|
this.$emit('select', month)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render (h) {
|
||||||
|
let months = t('months')
|
||||||
|
const currentYear = this.value && new Date(this.value).getFullYear()
|
||||||
|
const currentMonth = this.value && new Date(this.value).getMonth()
|
||||||
|
months = months.map((v, i) => {
|
||||||
|
return <span
|
||||||
|
class={{
|
||||||
|
'cell': true,
|
||||||
|
'actived': currentYear === this.calendarYear && currentMonth === i
|
||||||
|
}}
|
||||||
|
onClick={this.selectMonth.bind(this, i)}>
|
||||||
|
{v}
|
||||||
|
</span>
|
||||||
|
})
|
||||||
|
return <div class="mx-panel mx-panel-month">{months}</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { formatTime, parseTime } from '@/utils/index'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'panelTime',
|
||||||
|
props: {
|
||||||
|
timePickerOptions: {
|
||||||
|
type: [Object, Function],
|
||||||
|
default () {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minuteStep: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
validator: val => val >= 0 && val <= 60
|
||||||
|
},
|
||||||
|
value: null,
|
||||||
|
disabledTime: Function
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentHours () {
|
||||||
|
return new Date(this.value).getHours()
|
||||||
|
},
|
||||||
|
currentMinutes () {
|
||||||
|
return new Date(this.value).getMinutes()
|
||||||
|
},
|
||||||
|
currentSeconds () {
|
||||||
|
return new Date(this.value).getSeconds()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stringifyText (value) {
|
||||||
|
return ('00' + value).slice(String(value).length)
|
||||||
|
},
|
||||||
|
selectTime (time) {
|
||||||
|
if (typeof this.disabledTime === 'function' && this.disabledTime(time)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('select', new Date(time))
|
||||||
|
},
|
||||||
|
getTimeSelectOptions () {
|
||||||
|
const result = []
|
||||||
|
const options = this.timePickerOptions
|
||||||
|
if (!options) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
return options() || []
|
||||||
|
}
|
||||||
|
const start = parseTime(options.start)
|
||||||
|
const end = parseTime(options.end)
|
||||||
|
const step = parseTime(options.step)
|
||||||
|
if (start && end && step) {
|
||||||
|
const startMinutes = start.minutes + start.hours * 60
|
||||||
|
const endMinutes = end.minutes + end.hours * 60
|
||||||
|
const stepMinutes = step.minutes + step.hours * 60
|
||||||
|
const len = Math.floor((endMinutes - startMinutes) / stepMinutes)
|
||||||
|
for (let i = 0; i <= len; i++) {
|
||||||
|
let timeMinutes = startMinutes + i * stepMinutes
|
||||||
|
let hours = Math.floor(timeMinutes / 60)
|
||||||
|
let minutes = timeMinutes % 60
|
||||||
|
let value = {
|
||||||
|
hours, minutes
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
value,
|
||||||
|
label: formatTime(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
render (h) {
|
||||||
|
const date = new Date(this.value)
|
||||||
|
const disabledTime = typeof this.disabledTime === 'function' && this.disabledTime
|
||||||
|
|
||||||
|
let pickers = this.getTimeSelectOptions()
|
||||||
|
if (Array.isArray(pickers) && pickers.length) {
|
||||||
|
pickers = pickers.map(picker => {
|
||||||
|
const pickHours = picker.value.hours
|
||||||
|
const pickMinutes = picker.value.minutes
|
||||||
|
const time = new Date(date).setHours(pickHours, pickMinutes, 0)
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
class={{
|
||||||
|
'mx-time-picker-item': true,
|
||||||
|
'cell': true,
|
||||||
|
'actived': pickHours === this.currentHours && pickMinutes === this.currentMinutes,
|
||||||
|
'disabled': disabledTime && disabledTime(time)
|
||||||
|
}}
|
||||||
|
onClick={this.selectTime.bind(this, time)}>{picker.label}</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div class="mx-panel mx-panel-time">
|
||||||
|
<ul class="mx-time-list">
|
||||||
|
{pickers}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Array.apply(null, { length: 24 }).map((_, i) => {
|
||||||
|
const time = new Date(date).setHours(i)
|
||||||
|
return <li
|
||||||
|
class={{
|
||||||
|
'cell': true,
|
||||||
|
'actived': i === this.currentHours,
|
||||||
|
'disabled': disabledTime && disabledTime(time)
|
||||||
|
}}
|
||||||
|
onClick={this.selectTime.bind(this, time)}
|
||||||
|
>{this.stringifyText(i)}</li>
|
||||||
|
})
|
||||||
|
|
||||||
|
const step = this.minuteStep || 1
|
||||||
|
const length = parseInt(60 / step)
|
||||||
|
const minutes = Array.apply(null, { length }).map((_, i) => {
|
||||||
|
const value = i * step
|
||||||
|
const time = new Date(date).setMinutes(value)
|
||||||
|
return <li
|
||||||
|
class={{
|
||||||
|
'cell': true,
|
||||||
|
'actived': value === this.currentMinutes,
|
||||||
|
'disabled': disabledTime && disabledTime(time)
|
||||||
|
}}
|
||||||
|
onClick={this.selectTime.bind(this, time)}
|
||||||
|
>{this.stringifyText(value)}</li>
|
||||||
|
})
|
||||||
|
|
||||||
|
const seconds = Array.apply(null, { length: 60 }).map((_, i) => {
|
||||||
|
const time = new Date(date).setSeconds(i)
|
||||||
|
return <li
|
||||||
|
class={{
|
||||||
|
'cell': true,
|
||||||
|
'actived': i === this.currentSeconds,
|
||||||
|
'disabled': disabledTime && disabledTime(time)
|
||||||
|
}}
|
||||||
|
onClick={this.selectTime.bind(this, time)}
|
||||||
|
>{this.stringifyText(i)}</li>
|
||||||
|
})
|
||||||
|
|
||||||
|
let times = [hours, minutes]
|
||||||
|
if (this.minuteStep === 0) {
|
||||||
|
times.push(seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
times = times.map(list => (
|
||||||
|
<ul class="mx-time-list"
|
||||||
|
style={{ width: 100 / times.length + '%' }}>
|
||||||
|
{list}
|
||||||
|
</ul>
|
||||||
|
))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="mx-panel mx-panel-time">
|
||||||
|
{times}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'panelYear',
|
||||||
|
props: {
|
||||||
|
value: null,
|
||||||
|
firstYear: Number
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectYear (year) {
|
||||||
|
this.$emit('select', year)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render (h) {
|
||||||
|
// 当前年代
|
||||||
|
const firstYear = Math.floor(this.firstYear / 10) * 10
|
||||||
|
const currentYear = this.value && new Date(this.value).getFullYear()
|
||||||
|
const years = Array.apply(null, { length: 10 }).map((_, i) => {
|
||||||
|
const year = firstYear + i
|
||||||
|
return <span
|
||||||
|
class={{
|
||||||
|
'cell': true,
|
||||||
|
'actived': currentYear === year
|
||||||
|
}}
|
||||||
|
onClick={this.selectYear.bind(this, year)}
|
||||||
|
>{year}</span>
|
||||||
|
})
|
||||||
|
return <div class="mx-panel mx-panel-year">{years}</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
export function isPlainObject (obj) {
|
||||||
|
return Object.prototype.toString.call(obj) === '[object Object]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDateObejct (value) {
|
||||||
|
return value instanceof Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidDate (date) {
|
||||||
|
if (date === null || date === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !!new Date(date).getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidRange (date) {
|
||||||
|
return (
|
||||||
|
Array.isArray(date) &&
|
||||||
|
date.length === 2 &&
|
||||||
|
isValidDate(date[0]) &&
|
||||||
|
isValidDate(date[1]) &&
|
||||||
|
(new Date(date[1]).getTime() >= new Date(date[0]).getTime())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseTime (time) {
|
||||||
|
const values = (time || '').split(':')
|
||||||
|
if (values.length >= 2) {
|
||||||
|
const hours = parseInt(values[0], 10)
|
||||||
|
const minutes = parseInt(values[1], 10)
|
||||||
|
return {
|
||||||
|
hours,
|
||||||
|
minutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTime (time, type = '24') {
|
||||||
|
let hours = time.hours
|
||||||
|
hours = (type === '24') ? hours : (hours % 12 || 12)
|
||||||
|
hours = hours < 10 ? '0' + hours : hours
|
||||||
|
let minutes = time.minutes < 10 ? '0' + time.minutes : time.minutes
|
||||||
|
let result = hours + ':' + minutes
|
||||||
|
if (type === '12') {
|
||||||
|
result += time.hours >= 12 ? ' pm' : ' am'
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export default function scrollIntoView (container, selected) {
|
||||||
|
if (!selected) {
|
||||||
|
container.scrollTop = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetParents = []
|
||||||
|
let pointer = selected.offsetParent
|
||||||
|
while (pointer && container !== pointer && container.contains(pointer)) {
|
||||||
|
offsetParents.push(pointer)
|
||||||
|
pointer = pointer.offsetParent
|
||||||
|
}
|
||||||
|
const top = selected.offsetTop + offsetParents.reduce((prev, curr) => (prev + curr.offsetTop), 0)
|
||||||
|
const bottom = top + selected.offsetHeight
|
||||||
|
const viewRectTop = container.scrollTop
|
||||||
|
const viewRectBottom = viewRectTop + container.clientHeight
|
||||||
|
|
||||||
|
if (top < viewRectTop) {
|
||||||
|
container.scrollTop = top
|
||||||
|
} else if (bottom > viewRectBottom) {
|
||||||
|
container.scrollTop = bottom - container.clientHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,519 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import { mount, shallowMount } from '@vue/test-utils'
|
||||||
|
import { use } from '../src/locale/index'
|
||||||
|
import DatePicker from '../src/index.vue'
|
||||||
|
import CalendarPanel from '../src/calendar.vue'
|
||||||
|
|
||||||
|
import DatePanel from '../src/panel/date'
|
||||||
|
import TimePanel from '../src/panel/time'
|
||||||
|
import YearPanel from '../src/panel/year'
|
||||||
|
|
||||||
|
use('zh')
|
||||||
|
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe('datepicker', () => {
|
||||||
|
it('click: pick date', () => {
|
||||||
|
wrapper = mount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 2018-05-03
|
||||||
|
const vm = wrapper.vm
|
||||||
|
|
||||||
|
let td = wrapper.find('.mx-panel-date td:nth-child(5)')
|
||||||
|
expect(td.classes()).not.toContain('actived')
|
||||||
|
expect(vm.text).toBe('2018-05-02')
|
||||||
|
const time = new Date(2018, 4, 3).getTime()
|
||||||
|
td.trigger('click')
|
||||||
|
const emitted = wrapper.emitted()
|
||||||
|
expect(emitted.input[0][0].getTime()).toBe(time)
|
||||||
|
expect(emitted.change[0][0].getTime()).toBe(time)
|
||||||
|
wrapper.setProps({ value: emitted.input[0][0] })
|
||||||
|
td = wrapper.find('.mx-panel-date td:nth-child(5)')
|
||||||
|
expect(td.classes()).toContain('actived')
|
||||||
|
expect(vm.text).toBe('2018-05-03')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('click: clear icon', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = wrapper.vm
|
||||||
|
const clear = wrapper.find('.mx-clear-wrapper')
|
||||||
|
clear.trigger('click')
|
||||||
|
const emitted = wrapper.emitted()
|
||||||
|
expect(emitted.input[0][0]).toBe(null)
|
||||||
|
wrapper.setProps({ value: null })
|
||||||
|
expect(vm.text).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: range', (done) => {
|
||||||
|
wrapper = mount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
range: true,
|
||||||
|
value: [],
|
||||||
|
confirm: false
|
||||||
|
},
|
||||||
|
sync: false // sync bug
|
||||||
|
})
|
||||||
|
|
||||||
|
const calendars = wrapper.findAll(CalendarPanel)
|
||||||
|
const calendar1 = calendars.at(0)
|
||||||
|
const calendar2 = calendars.at(1)
|
||||||
|
|
||||||
|
const td1 = calendar1.findAll('.mx-panel-date tbody td')
|
||||||
|
const td2 = calendar2.findAll('.mx-panel-date tbody td')
|
||||||
|
|
||||||
|
td1.at(14).trigger('click')
|
||||||
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
let emitted = wrapper.emittedByOrder()
|
||||||
|
expect(emitted).toHaveLength(0)
|
||||||
|
expect(td1.at(14).classes()).toContain('actived')
|
||||||
|
expect(td2.at(13).classes()).toContain('disabled')
|
||||||
|
expect(td2.at(14).classes()).not.toContain('disabled')
|
||||||
|
|
||||||
|
const date1 = new Date(td1.at(14).element.title)
|
||||||
|
td2.at(16).trigger('click')
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
emitted = wrapper.emittedByOrder()
|
||||||
|
|
||||||
|
const date2 = new Date(td2.at(16).element.title)
|
||||||
|
|
||||||
|
expect(td2.at(16).classes()).toContain('actived')
|
||||||
|
expect(td1.at(15).classes()).toContain('inrange')
|
||||||
|
expect(td1.at(16).classes()).toContain('inrange')
|
||||||
|
expect(td1.at(17).classes()).toContain('disabled')
|
||||||
|
|
||||||
|
expect(emitted).toHaveLength(2)
|
||||||
|
expect(emitted[0].args[0]).toEqual([date1, date2])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: rangeSeparator', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
range: true,
|
||||||
|
value: [new Date('2018-06-01'), new Date('2018-06-10')],
|
||||||
|
rangeSeparator: '至'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = wrapper.vm
|
||||||
|
expect(vm.text).toBe('2018-06-01 至 2018-06-10')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: confirm', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
confirm: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = wrapper.vm
|
||||||
|
const btn = wrapper.find('.mx-datepicker-btn-confirm')
|
||||||
|
expect(btn.exists()).toBe(true)
|
||||||
|
// click the date expect popup don't close
|
||||||
|
wrapper.setData({ popupVisible: true })
|
||||||
|
vm.selectDate(new Date(2018, 5, 5))
|
||||||
|
expect(vm.popupVisible).toBe(true)
|
||||||
|
expect(wrapper.emittedByOrder()).toHaveLength(0)
|
||||||
|
btn.trigger('click')
|
||||||
|
expect(wrapper.emittedByOrder()).toHaveLength(3)
|
||||||
|
expect(vm.popupVisible).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: confirmText', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
confirm: true,
|
||||||
|
confirmText: '确定'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const btn = wrapper.find('.mx-datepicker-btn-confirm')
|
||||||
|
expect(btn.text()).toBe('确定')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: width', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
width: 300
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const el = wrapper.find('.mx-datepicker').element
|
||||||
|
let width = el.style.width
|
||||||
|
expect(width).toBe('300px')
|
||||||
|
wrapper.setProps({ width: '100%' })
|
||||||
|
width = el.style.width
|
||||||
|
expect(width).toBe('100%')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: disabled', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
disabled: true,
|
||||||
|
value: new Date(2018, 4, 5),
|
||||||
|
clearable: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = wrapper.vm
|
||||||
|
// don't show the clearIocn
|
||||||
|
expect(vm.showClearIcon).toBe(false)
|
||||||
|
// don't show the popup
|
||||||
|
vm.showPopup()
|
||||||
|
expect(vm.popupVisible).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: input attribte - inputName inputClass placeholder', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 5),
|
||||||
|
inputName: 'datepicker',
|
||||||
|
inputClass: ['mx-input', 'mx-my'],
|
||||||
|
placeholder: 'hehe'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const el = wrapper.find('.mx-input').element
|
||||||
|
|
||||||
|
expect(el.className).toBe('mx-input mx-my')
|
||||||
|
expect(el.getAttribute('name')).toBe('datepicker')
|
||||||
|
expect(el.getAttribute('placeholder')).toBe('hehe')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: lang', () => {
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
lang: 'en'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const el = wrapper.find('.mx-input').element
|
||||||
|
expect(el.getAttribute('placeholder')).toBe('Select Date')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: shortcuts', () => {
|
||||||
|
const today = new Date()
|
||||||
|
wrapper = shallowMount(DatePicker, {
|
||||||
|
propsData: {
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
text: 'Today',
|
||||||
|
start: today,
|
||||||
|
end: today
|
||||||
|
}
|
||||||
|
],
|
||||||
|
range: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let shortcuts = wrapper.findAll('.mx-shortcuts')
|
||||||
|
expect(shortcuts).toHaveLength(1)
|
||||||
|
shortcuts = shortcuts.at(0)
|
||||||
|
shortcuts.trigger('click')
|
||||||
|
expect(wrapper.emitted()).toEqual({
|
||||||
|
input: [[[today, today]]],
|
||||||
|
change: [[[today, today]]]
|
||||||
|
})
|
||||||
|
wrapper.setProps({
|
||||||
|
shortcuts: false
|
||||||
|
})
|
||||||
|
shortcuts = wrapper.find('.mx-shortcuts-wrapper')
|
||||||
|
expect(shortcuts.exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('calendar-panel', () => {
|
||||||
|
|
||||||
|
it('click: prev/next month', () => {
|
||||||
|
wrapper = mount(CalendarPanel)
|
||||||
|
|
||||||
|
const nextBtn = wrapper.find('.mx-icon-next-month')
|
||||||
|
const lastBtn = wrapper.find('.mx-icon-last-month')
|
||||||
|
const vm = wrapper.vm
|
||||||
|
let count = 12
|
||||||
|
while (count--) {
|
||||||
|
const oldYear = vm.calendarYear
|
||||||
|
const oldMonth = vm.calendarMonth
|
||||||
|
nextBtn.trigger('click')
|
||||||
|
const newYear = vm.calendarYear
|
||||||
|
const newMonth = vm.calendarMonth
|
||||||
|
if (oldMonth === 11) {
|
||||||
|
expect(newMonth).toBe(0)
|
||||||
|
expect(newYear).toBe(oldYear + 1)
|
||||||
|
} else {
|
||||||
|
expect(newMonth).toBe(oldMonth + 1)
|
||||||
|
expect(newYear).toBe(oldYear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count = 12
|
||||||
|
while (count--) {
|
||||||
|
const oldYear = vm.calendarYear
|
||||||
|
const oldMonth = vm.calendarMonth
|
||||||
|
lastBtn.trigger('click')
|
||||||
|
const newYear = vm.calendarYear
|
||||||
|
const newMonth = vm.calendarMonth
|
||||||
|
if (oldMonth === 0) {
|
||||||
|
expect(newMonth).toBe(11)
|
||||||
|
expect(newYear).toBe(oldYear - 1)
|
||||||
|
} else {
|
||||||
|
expect(newMonth).toBe(oldMonth - 1)
|
||||||
|
expect(newYear).toBe(oldYear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('click: prev/next year', () => {
|
||||||
|
wrapper = mount(CalendarPanel, {
|
||||||
|
value: new Date(2018,4,5)
|
||||||
|
})
|
||||||
|
const nextBtn = wrapper.find('.mx-icon-next-year')
|
||||||
|
const lastBtn = wrapper.find('.mx-icon-last-year')
|
||||||
|
const yearBtn = wrapper.find('.mx-current-year')
|
||||||
|
const vm = wrapper.vm
|
||||||
|
const oldYear = vm.calendarYear
|
||||||
|
expect(oldYear).toBe(2018)
|
||||||
|
nextBtn.trigger('click')
|
||||||
|
let newYear = vm.calendarYear
|
||||||
|
expect(newYear).toBe(2019)
|
||||||
|
lastBtn.trigger('click')
|
||||||
|
newYear = vm.calendarYear
|
||||||
|
expect(newYear).toBe(oldYear)
|
||||||
|
// 年视图测试
|
||||||
|
yearBtn.trigger('click')
|
||||||
|
expect(vm.panel).toBe('YEAR')
|
||||||
|
expect(vm.firstYear).toBe(2010)
|
||||||
|
nextBtn.trigger('click')
|
||||||
|
expect(vm.firstYear).toBe(2020)
|
||||||
|
lastBtn.trigger('click')
|
||||||
|
lastBtn.trigger('click')
|
||||||
|
expect(vm.firstYear).toBe(2000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: notBefore/notAfter', () => {
|
||||||
|
wrapper = mount(CalendarPanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 2),
|
||||||
|
notBefore: new Date(2018, 4, 1, 12),
|
||||||
|
notAfter: new Date(2018, 4, 31, 12)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const tds = wrapper.findAll('.mx-panel-date td')
|
||||||
|
for (let i = 0; i < 42; i++) {
|
||||||
|
const td = tds.at(i)
|
||||||
|
const classes = td.classes()
|
||||||
|
if (i < 2 || i > 32) {
|
||||||
|
expect(classes).toContain('disabled')
|
||||||
|
} else {
|
||||||
|
expect(classes).not.toContain('disabled')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: disabledDays(Array)', () => {
|
||||||
|
const disabledDays = ['2018-05-01', new Date(2018, 4, 3)]
|
||||||
|
wrapper = mount(CalendarPanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 2),
|
||||||
|
disabledDays
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const tds = wrapper.findAll('.mx-panel-date td.disabled')
|
||||||
|
expect(tds.length).toBe(disabledDays.length)
|
||||||
|
for (let i = 0, len = tds.length; i < len; i++) {
|
||||||
|
const tdDate = new Date(tds.at(i).element.title).getTime()
|
||||||
|
const expectDate = new Date(disabledDays[i]).setHours(0, 0, 0, 0)
|
||||||
|
expect(tdDate).toBe(expectDate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: disabledDays(Function)', () => {
|
||||||
|
const disabledDays = function (date) {
|
||||||
|
return date < new Date(2018, 4, 1) || date > new Date(2018, 4, 31)
|
||||||
|
}
|
||||||
|
wrapper = mount(CalendarPanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 4),
|
||||||
|
disabledDays
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const tds = wrapper.findAll('.mx-panel-date td')
|
||||||
|
for (let i = 0; i < 42; i++) {
|
||||||
|
const td = tds.at(i)
|
||||||
|
const classes = td.classes()
|
||||||
|
if (i < 2 || i > 32) {
|
||||||
|
expect(classes).toContain('disabled')
|
||||||
|
} else {
|
||||||
|
expect(classes).not.toContain('disabled')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('feat: when the time panel show, scroll to the right position', () => {
|
||||||
|
wrapper = mount(CalendarPanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 4),
|
||||||
|
type: 'datetime'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wrapper.setData({
|
||||||
|
panel: 'TIME'
|
||||||
|
})
|
||||||
|
const list = wrapper.find('.mx-time-list').element
|
||||||
|
list.scrollTop = 200
|
||||||
|
wrapper.setData({
|
||||||
|
panel: 'DATE'
|
||||||
|
})
|
||||||
|
wrapper.setData({
|
||||||
|
panel: 'TIME'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(list.scrollTop).toBe(0)
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('date-panel', () => {
|
||||||
|
const testRenderCalendar = (i) => it(`feat: render the corrent date panel firstDayOfWeek: ${i}`, () => {
|
||||||
|
wrapper = mount(DatePanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 1),
|
||||||
|
calendarMonth: 4,
|
||||||
|
calendarYear: 2018,
|
||||||
|
firstDayOfWeek: i
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const vm = wrapper.vm
|
||||||
|
const lastMonth = new Date(2018, 3, 30)
|
||||||
|
const lastMonthDay = 30
|
||||||
|
const lastMonthLength = (lastMonth.getDay() + 7 - i) % 7 + 1
|
||||||
|
const currentMonthLength = 31
|
||||||
|
const tds = wrapper.findAll('.mx-panel-date td')
|
||||||
|
for (let i = 0; i < 42; i++) {
|
||||||
|
const td = tds.at(i)
|
||||||
|
const text = parseInt(td.text(), 10)
|
||||||
|
const classes = td.classes()
|
||||||
|
if (i < lastMonthLength) {
|
||||||
|
expect(classes).toContain('last-month')
|
||||||
|
expect(text).toBe(lastMonthDay - lastMonthLength + 1 + i)
|
||||||
|
} else if (i < lastMonthLength + currentMonthLength) {
|
||||||
|
expect(text).toBe(i - lastMonthLength + 1)
|
||||||
|
expect(classes).toContain('cur-month')
|
||||||
|
if (text === 1) {
|
||||||
|
expect(classes).toContain('actived')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expect(text).toBe(i - lastMonthLength - currentMonthLength + 1)
|
||||||
|
expect(classes).toContain('next-month')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const week = ['一', '二', '三', '四', '五', '六', '日']
|
||||||
|
const firstWeek = wrapper.find('tr th').text()
|
||||||
|
expect(firstWeek).toBe(week[i - 1])
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let i = 1; i <= 7; i++) {
|
||||||
|
testRenderCalendar(i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('year-panel', () => {
|
||||||
|
it('feat: render the corrent year panel', () => {
|
||||||
|
wrapper = mount(YearPanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 4, 1),
|
||||||
|
firstYear: 2010
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const cells = wrapper.findAll('.cell')
|
||||||
|
|
||||||
|
for (let i = 0, len = cells.length; i < len; i++) {
|
||||||
|
const cell = cells.at(i)
|
||||||
|
expect(parseInt(cell.text())).toBe(2010 + i)
|
||||||
|
if (i === 8) {
|
||||||
|
expect(cell.classes()).toContain('actived')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.setProps({ firstYear: 2020 })
|
||||||
|
|
||||||
|
for (let i = 0, len = cells.length; i < len; i++) {
|
||||||
|
const cell = cells.at(i)
|
||||||
|
expect(parseInt(cell.text())).toBe(2020 + i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('time-panel', () => {
|
||||||
|
|
||||||
|
it('click: pick time emitted the select event', () => {
|
||||||
|
wrapper = mount(TimePanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 5, 5)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const list = wrapper.findAll('.mx-time-list')
|
||||||
|
expect(list).toHaveLength(3)
|
||||||
|
const hours = list.at(0).findAll('.cell')
|
||||||
|
const minutes = list.at(1).findAll('.cell')
|
||||||
|
const seconds = list.at(2).findAll('.cell')
|
||||||
|
|
||||||
|
expect(hours).toHaveLength(24)
|
||||||
|
expect(minutes).toHaveLength(60)
|
||||||
|
expect(seconds).toHaveLength(60)
|
||||||
|
|
||||||
|
hours.at(1).trigger('click')
|
||||||
|
minutes.at(1).trigger('click')
|
||||||
|
seconds.at(1).trigger('click')
|
||||||
|
expect(wrapper.emitted()).toEqual({
|
||||||
|
select: [[new Date(2018, 5, 5, 1)], [new Date(2018, 5, 5, 0, 1)], [new Date(2018, 5, 5, 0, 0, 1)]]
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: minuteStep', () => {
|
||||||
|
wrapper = mount(TimePanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 5, 5),
|
||||||
|
minuteStep: 30
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const list = wrapper.findAll('.mx-time-list')
|
||||||
|
expect(list).toHaveLength(2)
|
||||||
|
const minutes = list.at(1).findAll('.cell')
|
||||||
|
expect(minutes).toHaveLength(2)
|
||||||
|
expect(minutes.at(0).text()).toBe('00')
|
||||||
|
expect(minutes.at(1).text()).toBe('30')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prop: timePickerOptions', () => {
|
||||||
|
wrapper = mount(TimePanel, {
|
||||||
|
propsData: {
|
||||||
|
value: new Date(2018, 5, 5),
|
||||||
|
timePickerOptions: { start: '01:00', step: '00:30', end: '23:00' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const list = wrapper.findAll('.mx-time-list')
|
||||||
|
expect(list).toHaveLength(1)
|
||||||
|
const cells = list.at(0).findAll('.cell')
|
||||||
|
expect(cells).toHaveLength(45)
|
||||||
|
expect(cells.at(0).text()).toBe('01:00')
|
||||||
|
expect(cells.at(44).text()).toBe('23:00')
|
||||||
|
cells.at(0).trigger('click')
|
||||||
|
const emitted = wrapper.emitted()
|
||||||
|
expect(emitted).toEqual({
|
||||||
|
select: [[new Date(2018, 5, 5, 1)]]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
var path = require('path')
|
|
||||||
var webpack = require('webpack')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: './demo/main.js',
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, './demo'),
|
|
||||||
publicPath: '/demo/',
|
|
||||||
filename: 'build.js'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.vue$/,
|
|
||||||
loader: 'vue-loader',
|
|
||||||
options: {
|
|
||||||
loaders: {
|
|
||||||
}
|
|
||||||
// other vue-loader options go here
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
exclude: /node_modules/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpg|gif|svg)$/,
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: '[name].[ext]?[hash]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
historyApiFallback: true,
|
|
||||||
noInfo: true,
|
|
||||||
port: 9000
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
hints: false
|
|
||||||
},
|
|
||||||
devtool: '#eval-source-map'
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
const merge = require('webpack-merge')
|
|
||||||
const baseWebpackConfig = require('./webpack.config.js')
|
|
||||||
const webpack = require('webpack')
|
|
||||||
|
|
||||||
const webpackConfig = merge(baseWebpackConfig, {
|
|
||||||
devtool: 'source-map',
|
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env': {
|
|
||||||
NODE_ENV: '"production"'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
|
||||||
sourceMap: false,
|
|
||||||
compress: {
|
|
||||||
warnings: false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new webpack.LoaderOptionsPlugin({
|
|
||||||
minimize: true
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = webpackConfig
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const merge = require('webpack-merge')
|
|
||||||
const baseWebpackConfig = require('./webpack.config.js')
|
|
||||||
const webpack = require('webpack')
|
|
||||||
|
|
||||||
const webpackConfig = merge(baseWebpackConfig, {
|
|
||||||
entry: './index.js',
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, './dist'),
|
|
||||||
publicPath: '/dist/',
|
|
||||||
filename: 'build.js',
|
|
||||||
library: "DatePicker",
|
|
||||||
libraryTarget: "umd"
|
|
||||||
},
|
|
||||||
devtool: 'source-map',
|
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env': {
|
|
||||||
NODE_ENV: '"production"'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
|
||||||
sourceMap: false,
|
|
||||||
compress: {
|
|
||||||
warnings: false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new webpack.LoaderOptionsPlugin({
|
|
||||||
minimize: true
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = webpackConfig
|
|
||||||
Reference in New Issue
Block a user