mirror of https://github.com/ElemeFE/element
Calendar: add Calendar component (#14908)
parent
ac3aa99503
commit
dc8bdc021e
|
@ -72,5 +72,6 @@
|
||||||
"timeline-item": "./packages/timeline-item/index.js",
|
"timeline-item": "./packages/timeline-item/index.js",
|
||||||
"link": "./packages/link/index.js",
|
"link": "./packages/link/index.js",
|
||||||
"divider": "./packages/divider/index.js",
|
"divider": "./packages/divider/index.js",
|
||||||
"image": "./packages/image/index.js"
|
"image": "./packages/image/index.js",
|
||||||
|
"calendar": "./packages/calendar/index.js"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.demo-calendar.demo-block {
|
||||||
|
.is-selected {
|
||||||
|
color: #1989FA;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
@import "./badge.scss";
|
@import "./badge.scss";
|
||||||
@import "./border.scss";
|
@import "./border.scss";
|
||||||
@import "./button.scss";
|
@import "./button.scss";
|
||||||
|
@import "./calendar.scss";
|
||||||
@import "./card.scss";
|
@import "./card.scss";
|
||||||
@import "./carousel.scss";
|
@import "./carousel.scss";
|
||||||
@import "./cascader.scss";
|
@import "./cascader.scss";
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
## Calendar
|
||||||
|
|
||||||
|
Display date.
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
:::demo Set `value` to specify the currently displayed month. If `value` is not specified, current month is displayed. `value` supports two-way binding.
|
||||||
|
```html
|
||||||
|
<el-calendar v-model="value">
|
||||||
|
</el-calendar>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Custom Content
|
||||||
|
|
||||||
|
:::demo Customize what is displayed in the calendar cell by setting `scoped-slot` named `dateCell`. In `scoped-slot` you can get the date (the date of the current cell), data (including the type, isSelected, day attribute). For details, please refer to the API documentation below.
|
||||||
|
```html
|
||||||
|
<el-calendar>
|
||||||
|
<!-- Use 2.5 slot syntax. If you use Vue 2.6, please use new slot syntax-->
|
||||||
|
<template
|
||||||
|
slot="dateCell"
|
||||||
|
slot-scope="{date, data}">
|
||||||
|
<p :class="data.isSelected ? 'is-selected' : ''">
|
||||||
|
{{ data.day.split('-').slice(1).join('-') }} {{ data.isSelected ? '✔️' : ''}}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
<style>
|
||||||
|
.is-selected {
|
||||||
|
color: #1989FA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Range
|
||||||
|
|
||||||
|
:::demo Set the `range` attribute to specify the display range of the calendar. Start time must be Monday, end time must be Sunday, and the time span cannot exceed two months.
|
||||||
|
```html
|
||||||
|
<el-calendar :range="['2019-03-04', '2019-03-24']">
|
||||||
|
</el-calendar>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
| Attribute | Description | Type | Accepted Values | Default |
|
||||||
|
|-----------------|-------------- |---------- |---------------------- |--------- |
|
||||||
|
| value / v-model | binding value | Date/string/number | — | — |
|
||||||
|
| range | time range, including start time and end time. Start time must be Monday, end time must be Sunday, the time span cannot exceed two months | Array | — | — |
|
||||||
|
|
||||||
|
### dateCell scoped slot 参数
|
||||||
|
| Attribute | Description | Type | Accepted Values | Default |
|
||||||
|
|-----------------|-------------- |---------- |---------------------- |--------- |
|
||||||
|
| date | date the cell represents | Date | — | — |
|
||||||
|
| data | { type, isSelected, day}. `type` indicates which month the date belongs, optional values are prev-month, current-month, next-month; `isSelected` indicates whether the date is selected; `day` is the formatted date in the format yyyy-MM-dd | Object | — | — |
|
|
@ -0,0 +1,65 @@
|
||||||
|
## Calendar
|
||||||
|
|
||||||
|
Display date.
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
:::demo Set `value` to specify the currently displayed month. If `value` is not specified, current month is displayed. `value` supports two-way binding.
|
||||||
|
```html
|
||||||
|
<el-calendar v-model="value">
|
||||||
|
</el-calendar>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Custom Content
|
||||||
|
|
||||||
|
:::demo Customize what is displayed in the calendar cell by setting `scoped-slot` named `dateCell`. In `scoped-slot` you can get the date (the date of the current cell), data (including the type, isSelected, day attribute). For details, please refer to the API documentation below.
|
||||||
|
```html
|
||||||
|
<el-calendar>
|
||||||
|
<!-- Use 2.5 slot syntax. If you use Vue 2.6, please use new slot syntax-->
|
||||||
|
<template
|
||||||
|
slot="dateCell"
|
||||||
|
slot-scope="{date, data}">
|
||||||
|
<p :class="data.isSelected ? 'is-selected' : ''">
|
||||||
|
{{ data.day.split('-').slice(1).join('-') }} {{ data.isSelected ? '✔️' : ''}}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
<style>
|
||||||
|
.is-selected {
|
||||||
|
color: #1989FA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Range
|
||||||
|
|
||||||
|
:::demo Set the `range` attribute to specify the display range of the calendar. Start time must be Monday, end time must be Sunday, and the time span cannot exceed two months.
|
||||||
|
```html
|
||||||
|
<el-calendar :range="['2019-03-04', '2019-03-24']">
|
||||||
|
</el-calendar>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
| Attribute | Description | Type | Accepted Values | Default |
|
||||||
|
|-----------------|-------------- |---------- |---------------------- |--------- |
|
||||||
|
| value / v-model | binding value | Date/string/number | — | — |
|
||||||
|
| range | time range, including start time and end time. Start time must be Monday, end time must be Sunday, the time span cannot exceed two months | Array | — | — |
|
||||||
|
|
||||||
|
### dateCell scoped slot 参数
|
||||||
|
| Attribute | Description | Type | Accepted Values | Default |
|
||||||
|
|-----------------|-------------- |---------- |---------------------- |--------- |
|
||||||
|
| date | date the cell represents | Date | — | — |
|
||||||
|
| data | { type, isSelected, day}. `type` indicates which month the date belongs, optional values are prev-month, current-month, next-month; `isSelected` indicates whether the date is selected; `day` is the formatted date in the format yyyy-MM-dd | Object | — | — |
|
|
@ -0,0 +1,65 @@
|
||||||
|
## Calendar
|
||||||
|
|
||||||
|
Display date.
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
:::demo Set `value` to specify the currently displayed month. If `value` is not specified, current month is displayed. `value` supports two-way binding.
|
||||||
|
```html
|
||||||
|
<el-calendar v-model="value">
|
||||||
|
</el-calendar>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Custom Content
|
||||||
|
|
||||||
|
:::demo Customize what is displayed in the calendar cell by setting `scoped-slot` named `dateCell`. In `scoped-slot` you can get the date (the date of the current cell), data (including the type, isSelected, day attribute). For details, please refer to the API documentation below.
|
||||||
|
```html
|
||||||
|
<el-calendar>
|
||||||
|
<!-- Use 2.5 slot syntax. If you use Vue 2.6, please use new slot syntax-->
|
||||||
|
<template
|
||||||
|
slot="dateCell"
|
||||||
|
slot-scope="{date, data}">
|
||||||
|
<p :class="data.isSelected ? 'is-selected' : ''">
|
||||||
|
{{ data.day.split('-').slice(1).join('-') }} {{ data.isSelected ? '✔️' : ''}}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
<style>
|
||||||
|
.is-selected {
|
||||||
|
color: #1989FA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Range
|
||||||
|
|
||||||
|
:::demo Set the `range` attribute to specify the display range of the calendar. Start time must be Monday, end time must be Sunday, and the time span cannot exceed two months.
|
||||||
|
```html
|
||||||
|
<el-calendar :range="['2019-03-04', '2019-03-24']">
|
||||||
|
</el-calendar>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
| Attribute | Description | Type | Accepted Values | Default |
|
||||||
|
|-----------------|-------------- |---------- |---------------------- |--------- |
|
||||||
|
| value / v-model | binding value | Date/string/number | — | — |
|
||||||
|
| range | time range, including start time and end time. Start time must be Monday, end time must be Sunday, the time span cannot exceed two months | Array | — | — |
|
||||||
|
|
||||||
|
### dateCell scoped slot 参数
|
||||||
|
| Attribute | Description | Type | Accepted Values | Default |
|
||||||
|
|-----------------|-------------- |---------- |---------------------- |--------- |
|
||||||
|
| date | date the cell represents | Date | — | — |
|
||||||
|
| data | { type, isSelected, day}. `type` indicates which month the date belongs, optional values are prev-month, current-month, next-month; `isSelected` indicates whether the date is selected; `day` is the formatted date in the format yyyy-MM-dd | Object | — | — |
|
|
@ -0,0 +1,65 @@
|
||||||
|
## Calendar calendar
|
||||||
|
|
||||||
|
显示日期
|
||||||
|
|
||||||
|
### 基本
|
||||||
|
|
||||||
|
:::demo 设置 `value` 来指定当前显示的月份。如果 `value` 未指定,则显示当月。`value` 支持 `v-model` 双向绑定。
|
||||||
|
```html
|
||||||
|
<el-calendar v-model="value">
|
||||||
|
</el-calendar>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: new Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 自定义内容
|
||||||
|
|
||||||
|
:::demo 通过设置名为 `dateCell` 的 `scoped-slot` 来自定义日历单元格中显示的内容。在 `scoped-slot` 可以获取到 date(当前单元格的日期), data(包括 type,isSelected,day 属性)。详情解释参考下方的 API 文档。
|
||||||
|
```html
|
||||||
|
<el-calendar>
|
||||||
|
<!-- 这里使用的是 2.5 slot 语法,对于新项目请使用 2.6 slot 语法-->
|
||||||
|
<template
|
||||||
|
slot="dateCell"
|
||||||
|
slot-scope="{date, data}">
|
||||||
|
<p :class="data.isSelected ? 'is-selected' : ''">
|
||||||
|
{{ data.day.split('-').slice(1).join('-') }} {{ data.isSelected ? '✔️' : ''}}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
<style>
|
||||||
|
.is-selected {
|
||||||
|
color: #1989FA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 自定义范围
|
||||||
|
|
||||||
|
:::demo 设置 `range` 属性指定日历的显示范围。开始时间必须是周一,结束时间必须是周日,且时间跨度不能超过两个月。
|
||||||
|
```html
|
||||||
|
<el-calendar :range="['2019-03-04', '2019-03-24']">
|
||||||
|
</el-calendar>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|-----------------|-------------- |---------- |------------ |-------- |
|
||||||
|
| value / v-model | 绑定值 | Date/string/number | — | — |
|
||||||
|
| range | 时间范围,包括开始时间与结束时间。开始时间必须是周一,结束时间必须是周日,且时间跨度不能超过两个月。 | Array | — | — |
|
||||||
|
|
||||||
|
### dateCell scoped slot 参数
|
||||||
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|-----------------|-------------- |---------- |------------ |-------- |
|
||||||
|
| date | 单元格代表的日期 | Date | — | — |
|
||||||
|
| data | { type, isSelected, day},`type` 表示该日期的所属月份,可选值有 prev-month,current-month,next-month;`isSelected` 标明该日期是否被选中;`day` 是格式化的日期,格式为 yyyy-MM-dd | Object | — | — |
|
|
@ -260,6 +260,10 @@
|
||||||
"path": "/divider",
|
"path": "/divider",
|
||||||
"title": "Divider 分割线"
|
"title": "Divider 分割线"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/calendar",
|
||||||
|
"title": "Calendar 日历"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "/image",
|
"path": "/image",
|
||||||
"title": "Image 图片"
|
"title": "Image 图片"
|
||||||
|
@ -530,6 +534,10 @@
|
||||||
"path": "/divider",
|
"path": "/divider",
|
||||||
"title": "Divider"
|
"title": "Divider"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/calendar",
|
||||||
|
"title": "Calendar"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "/image",
|
"path": "/image",
|
||||||
"title": "Image"
|
"title": "Image"
|
||||||
|
@ -800,6 +808,10 @@
|
||||||
"path": "/divider",
|
"path": "/divider",
|
||||||
"title": "Divider"
|
"title": "Divider"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/calendar",
|
||||||
|
"title": "Calendar"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "/image",
|
"path": "/image",
|
||||||
"title": "Image"
|
"title": "Image"
|
||||||
|
@ -1070,6 +1082,10 @@
|
||||||
"path": "/divider",
|
"path": "/divider",
|
||||||
"title": "Divider"
|
"title": "Divider"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/calendar",
|
||||||
|
"title": "Calendar"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "/image",
|
"path": "/image",
|
||||||
"title": "Image"
|
"title": "Image"
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Calendar from './src/main';
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
Calendar.install = function(Vue) {
|
||||||
|
Vue.component(Calendar.name, Calendar);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Calendar;
|
|
@ -0,0 +1,189 @@
|
||||||
|
<script>
|
||||||
|
import fecha from 'element-ui/src/utils/date';
|
||||||
|
import { range as rangeArr, getFirstDayOfMonth, getPrevMonthLastDays, getMonthDays, getI18nSettings, validateRangeInOneMonth } from 'element-ui/src/utils/date-util';
|
||||||
|
export default {
|
||||||
|
|
||||||
|
props: {
|
||||||
|
selectedDay: String, // formated date yyyy-MM-dd
|
||||||
|
range: {
|
||||||
|
type: Array,
|
||||||
|
validator(val) {
|
||||||
|
if (!(val && val.length)) return true;
|
||||||
|
const [start, end] = val;
|
||||||
|
return validateRangeInOneMonth(start, end);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
date: Date,
|
||||||
|
hideHeader: Boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
inject: ['elCalendar'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toNestedArr(days) {
|
||||||
|
return rangeArr(days.length / 7).map((_, index) => {
|
||||||
|
const start = index * 7;
|
||||||
|
return days.slice(start, start + 7);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getFormateDate(day, type) {
|
||||||
|
if (!day || ['prev', 'current', 'next'].indexOf(type) === -1) {
|
||||||
|
throw new Error('invalid day or type');
|
||||||
|
}
|
||||||
|
let prefix = this.curMonthDatePrefix;
|
||||||
|
if (type === 'prev') {
|
||||||
|
prefix = this.prevMonthDatePrefix;
|
||||||
|
} else if (type === 'next') {
|
||||||
|
prefix = this.nextMonthDatePrefix;
|
||||||
|
}
|
||||||
|
day = `00${day}`.slice(-2);
|
||||||
|
return `${prefix}-${day}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
getCellClass({ text, type}) {
|
||||||
|
const classes = [type];
|
||||||
|
if (type === 'current') {
|
||||||
|
const date = this.getFormateDate(text, type);
|
||||||
|
if (date === this.selectedDay) {
|
||||||
|
classes.push('is-selected');
|
||||||
|
}
|
||||||
|
if (date === this.formatedToday) {
|
||||||
|
classes.push('is-today');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
|
||||||
|
pickDay({ text, type }) {
|
||||||
|
const date = this.getFormateDate(text, type);
|
||||||
|
this.$emit('pick', date);
|
||||||
|
},
|
||||||
|
|
||||||
|
cellRenderProxy({ text, type }) {
|
||||||
|
let render = this.elCalendar.$scopedSlots.dateCell;
|
||||||
|
if (!render) return <span>{ text }</span>;
|
||||||
|
|
||||||
|
const day = this.getFormateDate(text, type);
|
||||||
|
const date = new Date(day);
|
||||||
|
const data = {
|
||||||
|
isSelected: this.selectedDay === day,
|
||||||
|
type: `${type}-month`,
|
||||||
|
day
|
||||||
|
};
|
||||||
|
return render({ date, data });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
prevMonthDatePrefix() {
|
||||||
|
const temp = new Date(this.date.getTime());
|
||||||
|
temp.setDate(0);
|
||||||
|
return fecha.format(temp, 'yyyy-MM');
|
||||||
|
},
|
||||||
|
|
||||||
|
curMonthDatePrefix() {
|
||||||
|
return fecha.format(this.date, 'yyyy-MM');
|
||||||
|
},
|
||||||
|
|
||||||
|
nextMonthDatePrefix() {
|
||||||
|
const temp = new Date(this.date.getFullYear(), this.date.getMonth() + 1, 1);
|
||||||
|
return fecha.format(temp, 'yyyy-MM');
|
||||||
|
},
|
||||||
|
|
||||||
|
formatedToday() {
|
||||||
|
return this.elCalendar.formatedToday;
|
||||||
|
},
|
||||||
|
|
||||||
|
isInRange() {
|
||||||
|
return this.range && this.range.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
rows() {
|
||||||
|
let days = [];
|
||||||
|
// if range exists, should render days in range.
|
||||||
|
if (this.isInRange) {
|
||||||
|
const [start, end] = this.range;
|
||||||
|
const currentMonthRange = rangeArr(end.getDate() - start.getDate() + 1).map((_, index) => ({
|
||||||
|
text: start.getDate() + index,
|
||||||
|
type: 'current'
|
||||||
|
}));
|
||||||
|
let remaining = currentMonthRange.length % 7;
|
||||||
|
remaining = remaining === 0 ? 0 : 7 - remaining;
|
||||||
|
const nextMonthRange = rangeArr(remaining).map((_, index) => ({
|
||||||
|
text: index + 1,
|
||||||
|
type: 'next'
|
||||||
|
}));
|
||||||
|
days = currentMonthRange.concat(nextMonthRange);
|
||||||
|
} else {
|
||||||
|
const date = this.date;
|
||||||
|
const firstDay = getFirstDayOfMonth(date);
|
||||||
|
const prevMonthDays = getPrevMonthLastDays(date, firstDay - 1).map(day => ({
|
||||||
|
text: day,
|
||||||
|
type: 'prev'
|
||||||
|
}));
|
||||||
|
const currentMonthDays = getMonthDays(date).map(day => ({
|
||||||
|
text: day,
|
||||||
|
type: 'current'
|
||||||
|
}));
|
||||||
|
days = [...prevMonthDays, ...currentMonthDays];
|
||||||
|
const nextMonthDays = rangeArr(42 - days.length).map((_, index) => ({
|
||||||
|
text: index + 1,
|
||||||
|
type: 'next'
|
||||||
|
}));
|
||||||
|
days = days.concat(nextMonthDays);
|
||||||
|
}
|
||||||
|
return this.toNestedArr(days);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const dayNames = getI18nSettings().dayNames;
|
||||||
|
return {
|
||||||
|
DAYS: dayNames.slice(1).concat(dayNames[0])
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const thead = this.hideHeader ? null : (<thead>
|
||||||
|
{
|
||||||
|
this.DAYS.map(day => <th key={day}>{ day }</th>)
|
||||||
|
}
|
||||||
|
</thead>);
|
||||||
|
return (
|
||||||
|
<table
|
||||||
|
class={{
|
||||||
|
'el-calendar-table': true,
|
||||||
|
'is-range': this.isInRange
|
||||||
|
}}
|
||||||
|
cellspacing="0"
|
||||||
|
cellpadding="0">
|
||||||
|
{
|
||||||
|
thead
|
||||||
|
}
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
this.rows.map((row, index) => <tr
|
||||||
|
class={{
|
||||||
|
'el-calendar-table__row': true,
|
||||||
|
'el-calendar-table__row--hide-border': index === 0 && this.hideHeader
|
||||||
|
}}
|
||||||
|
key={index}>
|
||||||
|
{
|
||||||
|
row.map((cell, key) => <td key={key}
|
||||||
|
class={ this.getCellClass(cell) }
|
||||||
|
onClick={this.pickDay.bind(this, cell)}>
|
||||||
|
<div class="el-calendar-day">
|
||||||
|
{
|
||||||
|
this.cellRenderProxy(cell)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>)
|
||||||
|
}
|
||||||
|
</tr>)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,251 @@
|
||||||
|
<template>
|
||||||
|
<div class="el-calendar">
|
||||||
|
<div class="el-calendar__header">
|
||||||
|
<div class="el-calendar__title">
|
||||||
|
{{ i18nDate }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="el-calendar__button-group"
|
||||||
|
v-if="validatedRange.length === 0">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button
|
||||||
|
type="plain"
|
||||||
|
size="mini"
|
||||||
|
@click="selectDate('prev-month')">
|
||||||
|
{{ t('el.datepicker.prevMonth') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="plain"
|
||||||
|
size="mini"
|
||||||
|
@click="selectDate('today')">
|
||||||
|
{{ t('el.datepicker.today') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="plain"
|
||||||
|
size="mini"
|
||||||
|
@click="selectDate('next-month')">
|
||||||
|
{{ t('el.datepicker.nextMonth') }}
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="el-calendar__body"
|
||||||
|
v-if="validatedRange.length === 0"
|
||||||
|
key="no-range">
|
||||||
|
<date-table
|
||||||
|
:date="date"
|
||||||
|
:selected-day="realSelectedDay"
|
||||||
|
@pick="pickDay" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="el-calendar__body"
|
||||||
|
key="has-range">
|
||||||
|
<date-table
|
||||||
|
v-for="(range, index) in validatedRange"
|
||||||
|
:key="index"
|
||||||
|
:date="range[0]"
|
||||||
|
:selected-day="realSelectedDay"
|
||||||
|
:range="range"
|
||||||
|
:hide-header="index !== 0"
|
||||||
|
@pick="pickDay" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
|
import fecha from 'element-ui/src/utils/date';
|
||||||
|
import DateTable from './date-table';
|
||||||
|
import { validateRangeInOneMonth } from 'element-ui/src/utils/date-util';
|
||||||
|
|
||||||
|
const validTypes = ['prev-month', 'today', 'next-month'];
|
||||||
|
const oneDay = 86400000;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ElCalendar',
|
||||||
|
|
||||||
|
mixins: [Locale],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DateTable
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: [Date, String, Number],
|
||||||
|
range: {
|
||||||
|
type: Array,
|
||||||
|
validator(range) {
|
||||||
|
if (Array.isArray(range)) {
|
||||||
|
return range.length === 2 && range.every(
|
||||||
|
item => typeof item === 'string' ||
|
||||||
|
typeof item === 'number' ||
|
||||||
|
item instanceof Date);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
elCalendar: this
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
pickDay(day) {
|
||||||
|
this.realSelectedDay = day;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectDate(type) {
|
||||||
|
if (validTypes.indexOf(type) === -1) {
|
||||||
|
throw new Error(`invalid type ${type}`);
|
||||||
|
}
|
||||||
|
let day = '';
|
||||||
|
if (type === 'prev-month') {
|
||||||
|
day = `${this.prevMonthDatePrefix}-01`;
|
||||||
|
} else if (type === 'next-month') {
|
||||||
|
day = `${this.nextMonthDatePrefix}-01`;
|
||||||
|
} else {
|
||||||
|
day = this.formatedToday;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (day === this.formatedDate) return;
|
||||||
|
this.pickDay(day);
|
||||||
|
},
|
||||||
|
|
||||||
|
toDate(val) {
|
||||||
|
if (!val) {
|
||||||
|
throw new Error('invalid val');
|
||||||
|
}
|
||||||
|
return val instanceof Date ? val : new Date(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
prevMonthDatePrefix() {
|
||||||
|
const temp = new Date(this.date.getTime());
|
||||||
|
temp.setDate(0);
|
||||||
|
return fecha.format(temp, 'yyyy-MM');
|
||||||
|
},
|
||||||
|
|
||||||
|
curMonthDatePrefix() {
|
||||||
|
return fecha.format(this.date, 'yyyy-MM');
|
||||||
|
},
|
||||||
|
|
||||||
|
nextMonthDatePrefix() {
|
||||||
|
const temp = new Date(this.date.getFullYear(), this.date.getMonth() + 1, 1);
|
||||||
|
return fecha.format(temp, 'yyyy-MM');
|
||||||
|
},
|
||||||
|
|
||||||
|
formatedDate() {
|
||||||
|
return fecha.format(this.date, 'yyyy-MM-dd');
|
||||||
|
},
|
||||||
|
|
||||||
|
i18nDate() {
|
||||||
|
const year = this.formatedDate.slice(0, 4);
|
||||||
|
const month = this.formatedDate.slice(5, 7).replace('0', '');
|
||||||
|
return `${year} ${this.t('el.datepicker.year')} ${this.t('el.datepicker.month' + month)}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
formatedToday() {
|
||||||
|
return fecha.format(this.now, 'yyyy-MM-dd');
|
||||||
|
},
|
||||||
|
|
||||||
|
realSelectedDay: {
|
||||||
|
get() {
|
||||||
|
if (!this.value) return this.selectedDay;
|
||||||
|
return this.formatedDate;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.selectedDay = val;
|
||||||
|
const date = new Date(val);
|
||||||
|
this.$emit('input', date);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
date() {
|
||||||
|
if (!this.value) {
|
||||||
|
if (this.realSelectedDay) {
|
||||||
|
return new Date(this.selectedDay);
|
||||||
|
} else if (this.validatedRange.length) {
|
||||||
|
return this.validatedRange[0][0];
|
||||||
|
}
|
||||||
|
return this.now;
|
||||||
|
} else {
|
||||||
|
return this.toDate(this.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// if range is valid, we get a two-digit array
|
||||||
|
validatedRange() {
|
||||||
|
let range = this.range;
|
||||||
|
if (!range) return [];
|
||||||
|
const expetedMap = {
|
||||||
|
0: {
|
||||||
|
value: 1,
|
||||||
|
message: 'start of range should be Monday.'
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
value: 0,
|
||||||
|
message: 'end of range should be Sunday.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
range = range.reduce((prev, val, index) => {
|
||||||
|
const date = this.toDate(val);
|
||||||
|
if (date.getDay() !== expetedMap[index].value) {
|
||||||
|
console.warn('[ElementCalendar]', expetedMap[index].message, ' invalid range will be ignored');
|
||||||
|
} else {
|
||||||
|
prev = prev.concat(date);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
if (range.length === 2) {
|
||||||
|
const [start, end] = range;
|
||||||
|
if (start > end) {
|
||||||
|
console.warn('[ElementCalendar]end time should be greater than start time');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// start time and end time in one month
|
||||||
|
if (validateRangeInOneMonth(start, end)) {
|
||||||
|
return [
|
||||||
|
[start, end]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const data = [];
|
||||||
|
let startDay = new Date(start.getFullYear(), start.getMonth() + 1, 1);
|
||||||
|
const lastDay = this.toDate(startDay.getTime() - oneDay);
|
||||||
|
if (!validateRangeInOneMonth(startDay, end)) {
|
||||||
|
console.warn('[ElementCalendar]start time and end time interval must not exceed two months');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
data.push([
|
||||||
|
start,
|
||||||
|
lastDay
|
||||||
|
]);
|
||||||
|
let interval = startDay.getDay();
|
||||||
|
interval = interval <= 1 ? Math.abs(interval - 1) : (8 - interval);
|
||||||
|
startDay = this.toDate(startDay.getTime() + interval * oneDay);
|
||||||
|
if (startDay.getDate() < end.getDate()) {
|
||||||
|
data.push([
|
||||||
|
startDay,
|
||||||
|
end
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedDay: '',
|
||||||
|
now: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -32,7 +32,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, prevDate, nextDate, isDate, clearTime as _clearTime} from '../util';
|
import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, prevDate, nextDate, isDate, clearTime as _clearTime} from 'element-ui/src/utils/date-util';
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import { arrayFindIndex, arrayFind, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
|
import { arrayFindIndex, arrayFind, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import { isDate, range, getDayCountOfMonth, nextDate } from '../util';
|
import { isDate, range, getDayCountOfMonth, nextDate } from 'element-ui/src/utils/date-util';
|
||||||
import { hasClass } from 'element-ui/src/utils/dom';
|
import { hasClass } from 'element-ui/src/utils/dom';
|
||||||
import { arrayFindIndex, coerceTruthyValueToArray, arrayFind } from 'element-ui/src/utils/util';
|
import { arrayFindIndex, coerceTruthyValueToArray, arrayFind } from 'element-ui/src/utils/util';
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
import { getRangeHours, getRangeMinutes, modifyTime } from '../util';
|
import { getRangeHours, getRangeMinutes, modifyTime } from 'element-ui/src/utils/date-util';
|
||||||
import ElScrollbar from 'element-ui/packages/scrollbar';
|
import ElScrollbar from 'element-ui/packages/scrollbar';
|
||||||
import RepeatClick from 'element-ui/src/directives/repeat-click';
|
import RepeatClick from 'element-ui/src/directives/repeat-click';
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
import { hasClass } from 'element-ui/src/utils/dom';
|
import { hasClass } from 'element-ui/src/utils/dom';
|
||||||
import { isDate, range, nextDate, getDayCountOfYear } from '../util';
|
import { isDate, range, nextDate, getDayCountOfYear } from 'element-ui/src/utils/date-util';
|
||||||
import { arrayFindIndex, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
|
import { arrayFindIndex, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
|
||||||
|
|
||||||
const datesInYear = year => {
|
const datesInYear = year => {
|
||||||
|
|
|
@ -200,7 +200,7 @@
|
||||||
nextDate,
|
nextDate,
|
||||||
extractDateFormat,
|
extractDateFormat,
|
||||||
extractTimeFormat
|
extractTimeFormat
|
||||||
} from '../util';
|
} from 'element-ui/src/utils/date-util';
|
||||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import TimePicker from './time';
|
import TimePicker from './time';
|
||||||
|
|
|
@ -160,7 +160,7 @@
|
||||||
extractDateFormat,
|
extractDateFormat,
|
||||||
extractTimeFormat,
|
extractTimeFormat,
|
||||||
timeWithinRange
|
timeWithinRange
|
||||||
} from '../util';
|
} from 'element-ui/src/utils/date-util';
|
||||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import ElInput from 'element-ui/packages/input';
|
import ElInput from 'element-ui/packages/input';
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
prevYear,
|
prevYear,
|
||||||
nextYear,
|
nextYear,
|
||||||
nextMonth
|
nextMonth
|
||||||
} from '../util';
|
} from 'element-ui/src/utils/date-util';
|
||||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import MonthTable from '../basic/month-table';
|
import MonthTable from '../basic/month-table';
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
modifyDate,
|
modifyDate,
|
||||||
clearMilliseconds,
|
clearMilliseconds,
|
||||||
timeWithinRange
|
timeWithinRange
|
||||||
} from '../util';
|
} from 'element-ui/src/utils/date-util';
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import TimeSpinner from '../basic/time-spinner';
|
import TimeSpinner from '../basic/time-spinner';
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
import { limitTimeRange, isDate, clearMilliseconds, timeWithinRange } from '../util';
|
import { limitTimeRange, isDate, clearMilliseconds, timeWithinRange } from 'element-ui/src/utils/date-util';
|
||||||
import Locale from 'element-ui/src/mixins/locale';
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
import TimeSpinner from '../basic/time-spinner';
|
import TimeSpinner from '../basic/time-spinner';
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||||
import { formatDate, parseDate, isDateObject, getWeekNumber } from './util';
|
import { formatDate, parseDate, isDateObject, getWeekNumber } from 'element-ui/src/utils/date-util';
|
||||||
import Popper from 'element-ui/src/utils/vue-popper';
|
import Popper from 'element-ui/src/utils/vue-popper';
|
||||||
import Emitter from 'element-ui/src/mixins/emitter';
|
import Emitter from 'element-ui/src/mixins/emitter';
|
||||||
import ElInput from 'element-ui/packages/input';
|
import ElInput from 'element-ui/packages/input';
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
@import "mixins/mixins";
|
||||||
|
@import "common/var";
|
||||||
|
|
||||||
|
@include b(calendar) {
|
||||||
|
background-color:#fff;
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-bottom: $--table-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
color: #000000;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(body) {
|
||||||
|
padding: 12px 20px 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(calendar-table) {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
padding: 12px 0;
|
||||||
|
color: $--color-text-regular;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.is-range) {
|
||||||
|
td.prev,
|
||||||
|
td.next {
|
||||||
|
color: $--color-text-placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border-bottom: $--calendar-border;
|
||||||
|
border-right: $--calendar-border;
|
||||||
|
vertical-align: top;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
@include when(selected) {
|
||||||
|
background-color: $--calendar-selected-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(today) {
|
||||||
|
color: $--color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:first-child td {
|
||||||
|
border-top: $--calendar-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr td:first-child {
|
||||||
|
border-left: $--calendar-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.el-calendar-table__row--hide-border td {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(calendar-day) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
height: $--calendar-cell-width;
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: $--calendar-selected-background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -934,6 +934,13 @@ $--link-warning-font-color: $--color-warning !default;
|
||||||
$--link-danger-font-color: $--color-danger !default;
|
$--link-danger-font-color: $--color-danger !default;
|
||||||
/// color||Color|0
|
/// color||Color|0
|
||||||
$--link-info-font-color: $--color-info !default;
|
$--link-info-font-color: $--color-info !default;
|
||||||
|
/* Calendar
|
||||||
|
--------------------------*/
|
||||||
|
/// border||Other|4
|
||||||
|
$--calendar-border: $--table-border !default;
|
||||||
|
/// color||Other|4
|
||||||
|
$--calendar-selected-background-color: #F2F8FE !default;
|
||||||
|
$--calendar-cell-width: 85px !default;
|
||||||
|
|
||||||
/* Break-point
|
/* Break-point
|
||||||
--------------------------*/
|
--------------------------*/
|
||||||
|
|
|
@ -70,3 +70,4 @@
|
||||||
@import "./link.scss";
|
@import "./link.scss";
|
||||||
@import "./divider.scss";
|
@import "./divider.scss";
|
||||||
@import "./image.scss";
|
@import "./image.scss";
|
||||||
|
@import "./calendar.scss";
|
||||||
|
|
|
@ -74,6 +74,7 @@ import TimelineItem from '../packages/timeline-item/index.js';
|
||||||
import Link from '../packages/link/index.js';
|
import Link from '../packages/link/index.js';
|
||||||
import Divider from '../packages/divider/index.js';
|
import Divider from '../packages/divider/index.js';
|
||||||
import Image from '../packages/image/index.js';
|
import Image from '../packages/image/index.js';
|
||||||
|
import Calendar from '../packages/calendar/index.js';
|
||||||
import locale from 'element-ui/src/locale';
|
import locale from 'element-ui/src/locale';
|
||||||
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
|
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
|
||||||
|
|
||||||
|
@ -148,6 +149,7 @@ const components = [
|
||||||
Link,
|
Link,
|
||||||
Divider,
|
Divider,
|
||||||
Image,
|
Image,
|
||||||
|
Calendar,
|
||||||
CollapseTransition
|
CollapseTransition
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -260,5 +262,6 @@ export default {
|
||||||
TimelineItem,
|
TimelineItem,
|
||||||
Link,
|
Link,
|
||||||
Divider,
|
Divider,
|
||||||
Image
|
Image,
|
||||||
|
Calendar
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,8 @@
|
||||||
import dateUtil from 'element-ui/src/utils/date';
|
import fecha from 'element-ui/src/utils/date';
|
||||||
import { t } from 'element-ui/src/locale';
|
import { t } from 'element-ui/src/locale';
|
||||||
|
|
||||||
const weeks = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
const weeks = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||||
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
||||||
const getI18nSettings = () => {
|
|
||||||
return {
|
|
||||||
dayNamesShort: weeks.map(week => t(`el.datepicker.weeks.${ week }`)),
|
|
||||||
dayNames: weeks.map(week => t(`el.datepicker.weeks.${ week }`)),
|
|
||||||
monthNamesShort: months.map(month => t(`el.datepicker.months.${ month }`)),
|
|
||||||
monthNames: months.map((month, index) => t(`el.datepicker.month${ index + 1 }`)),
|
|
||||||
amPm: ['am', 'pm']
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const newArray = function(start, end) {
|
const newArray = function(start, end) {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
@ -21,6 +12,16 @@ const newArray = function(start, end) {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getI18nSettings = () => {
|
||||||
|
return {
|
||||||
|
dayNamesShort: weeks.map(week => t(`el.datepicker.weeks.${ week }`)),
|
||||||
|
dayNames: weeks.map(week => t(`el.datepicker.weeks.${ week }`)),
|
||||||
|
monthNamesShort: months.map(month => t(`el.datepicker.months.${ month }`)),
|
||||||
|
monthNames: months.map((month, index) => t(`el.datepicker.month${ index + 1 }`)),
|
||||||
|
amPm: ['am', 'pm']
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const toDate = function(date) {
|
export const toDate = function(date) {
|
||||||
return isDate(date) ? new Date(date) : null;
|
return isDate(date) ? new Date(date) : null;
|
||||||
};
|
};
|
||||||
|
@ -39,11 +40,11 @@ export const isDateObject = function(val) {
|
||||||
export const formatDate = function(date, format) {
|
export const formatDate = function(date, format) {
|
||||||
date = toDate(date);
|
date = toDate(date);
|
||||||
if (!date) return '';
|
if (!date) return '';
|
||||||
return dateUtil.format(date, format || 'yyyy-MM-dd', getI18nSettings());
|
return fecha.format(date, format || 'yyyy-MM-dd', getI18nSettings());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseDate = function(string, format) {
|
export const parseDate = function(string, format) {
|
||||||
return dateUtil.parse(string, format || 'yyyy-MM-dd', getI18nSettings());
|
return fecha.parse(string, format || 'yyyy-MM-dd', getI18nSettings());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDayCountOfMonth = function(year, month) {
|
export const getDayCountOfMonth = function(year, month) {
|
||||||
|
@ -131,6 +132,20 @@ export const getRangeHours = function(ranges) {
|
||||||
return hours;
|
return hours;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPrevMonthLastDays = (date, amount) => {
|
||||||
|
if (amount <= 0) return [];
|
||||||
|
const temp = new Date(date.getTime());
|
||||||
|
temp.setDate(0);
|
||||||
|
const lastDay = temp.getDate();
|
||||||
|
return range(amount).map((_, index) => lastDay - (amount - index - 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMonthDays = (date) => {
|
||||||
|
const temp = new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
||||||
|
const days = temp.getDate();
|
||||||
|
return range(days).map((_, index) => index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
function setRangeData(arr, start, end, value) {
|
function setRangeData(arr, start, end, value) {
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
arr[i] = value;
|
arr[i] = value;
|
||||||
|
@ -196,7 +211,7 @@ export const clearMilliseconds = function(date) {
|
||||||
export const limitTimeRange = function(date, ranges, format = 'HH:mm:ss') {
|
export const limitTimeRange = function(date, ranges, format = 'HH:mm:ss') {
|
||||||
// TODO: refactory a more elegant solution
|
// TODO: refactory a more elegant solution
|
||||||
if (ranges.length === 0) return date;
|
if (ranges.length === 0) return date;
|
||||||
const normalizeDate = date => dateUtil.parse(dateUtil.format(date, format), format);
|
const normalizeDate = date => fecha.parse(fecha.format(date, format), format);
|
||||||
const ndate = normalizeDate(date);
|
const ndate = normalizeDate(date);
|
||||||
const nranges = ranges.map(range => range.map(normalizeDate));
|
const nranges = ranges.map(range => range.map(normalizeDate));
|
||||||
if (nranges.some(nrange => ndate >= nrange[0] && ndate <= nrange[1])) return date;
|
if (nranges.some(nrange => ndate >= nrange[0] && ndate <= nrange[1])) return date;
|
||||||
|
@ -271,3 +286,7 @@ export const extractTimeFormat = function(format) {
|
||||||
.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?y{2,4}/g, '')
|
.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?y{2,4}/g, '')
|
||||||
.trim();
|
.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateRangeInOneMonth = function(start, end) {
|
||||||
|
return (start.getMonth() === end.getMonth()) && (start.getFullYear() === end.getFullYear());
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { createVue, destroyVM, waitImmediate } from '../util';
|
||||||
|
|
||||||
|
describe('Calendar', () => {
|
||||||
|
let vm;
|
||||||
|
afterEach(() => {
|
||||||
|
destroyVM(vm);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create', async() => {
|
||||||
|
vm = createVue({
|
||||||
|
template: `
|
||||||
|
<el-calendar v-model="value"></el-calendar>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: new Date('2019-04-01')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
const titleEl = vm.$el.querySelector('.el-calendar__title');
|
||||||
|
expect(/2019.*4/.test(titleEl.innerText)).to.be.true;
|
||||||
|
expect(vm.$el.querySelectorAll('thead th').length).to.equal(7);
|
||||||
|
const rows = vm.$el.querySelectorAll('.el-calendar-table__row');
|
||||||
|
expect(rows.length).to.equal(6);
|
||||||
|
rows[5].firstElementChild.click();
|
||||||
|
|
||||||
|
await waitImmediate();
|
||||||
|
|
||||||
|
expect(/2019.*5/.test(titleEl.innerText)).to.be.true;
|
||||||
|
const value = vm.value;
|
||||||
|
expect(value.getFullYear()).to.be.equal(2019);
|
||||||
|
expect(value.getMonth()).to.be.equal(4);
|
||||||
|
expect(vm.$el.querySelector('.is-selected span').innerText).to.be.equal('6');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('range', () => {
|
||||||
|
vm = createVue({
|
||||||
|
template: `
|
||||||
|
<el-calendar :range="['2019-03-04', '2019-03-24']"></el-calendar>
|
||||||
|
`
|
||||||
|
}, true);
|
||||||
|
const titleEl = vm.$el.querySelector('.el-calendar__title');
|
||||||
|
expect(/2019.*3/.test(titleEl.innerText)).to.be.true;
|
||||||
|
const rows = vm.$el.querySelectorAll('.el-calendar-table__row');
|
||||||
|
expect(rows.length).to.equal(3);
|
||||||
|
expect(vm.$el.querySelector('.el-calendar__button-group')).to.be.a('null');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('range tow monthes', async() => {
|
||||||
|
vm = createVue({
|
||||||
|
template: `
|
||||||
|
<el-calendar :range="['2019-04-15', '2019-05-19']"></el-calendar>
|
||||||
|
`
|
||||||
|
}, true);
|
||||||
|
const titleEl = vm.$el.querySelector('.el-calendar__title');
|
||||||
|
expect(/2019.*4/.test(titleEl.innerText)).to.be.true;
|
||||||
|
const dateTables = vm.$el.querySelectorAll('.el-calendar-table.is-range');
|
||||||
|
expect(dateTables.length).to.be.equal(2);
|
||||||
|
const rows = vm.$el.querySelectorAll('.el-calendar-table__row');
|
||||||
|
expect(rows.length).to.equal(5);
|
||||||
|
const cell = rows[rows.length - 1].firstElementChild;
|
||||||
|
cell.click();
|
||||||
|
|
||||||
|
await waitImmediate();
|
||||||
|
|
||||||
|
expect(/2019.*5/.test(titleEl.innerText)).to.be.true;
|
||||||
|
expect(cell.classList.contains('is-selected')).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ElementUIComponent } from './component'
|
||||||
|
|
||||||
|
export type DateType = Date | String | Number
|
||||||
|
|
||||||
|
/** Calendar Component */
|
||||||
|
export declare class ElCalendar extends ElementUIComponent {
|
||||||
|
/** Binding value */
|
||||||
|
value: DateType
|
||||||
|
|
||||||
|
/** Specify the display range of the calendar */
|
||||||
|
range: DateType[]
|
||||||
|
}
|
|
@ -70,6 +70,8 @@ import { ElTransfer } from './transfer'
|
||||||
import { ElTree } from './tree'
|
import { ElTree } from './tree'
|
||||||
import { ElUpload } from './upload'
|
import { ElUpload } from './upload'
|
||||||
import { ElDivider } from './divider'
|
import { ElDivider } from './divider'
|
||||||
|
import { ElCalendar } from './calendar'
|
||||||
|
import { ElImage } from './image'
|
||||||
|
|
||||||
export interface InstallationOptions {
|
export interface InstallationOptions {
|
||||||
locale: any,
|
locale: any,
|
||||||
|
|
Loading…
Reference in New Issue