diff --git a/src/components/date-picker/base/date-table.vue b/src/components/date-picker/base/date-table.vue index 07beb3e..ee83b6b 100644 --- a/src/components/date-picker/base/date-table.vue +++ b/src/components/date-picker/base/date-table.vue @@ -6,7 +6,7 @@ <div :class="[prefixCls + '-header']"> <span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span> </div> - <span :class="getCellCls(cell)" v-for="cell in cells"><em>{{ cell.text }}</em></span> + <span :class="getCellCls(cell)" v-for="cell in cells"><em :index="$index">{{ cell.text }}</em></span> </div> </template> <script> @@ -126,8 +126,40 @@ } }, methods: { - handleClick () { + handleClick (event) { + const target = event.target; + if (target.tagName === 'EM') { + const cell = this.cells[parseInt(event.target.getAttribute('index'))]; + if (cell.disabled) return; + let year = this.year; + let month = this.month; + let day = cell.text; + + if (cell.type === 'prev-month') { + if (month === 0) { + month = 11; + year--; + } else { + month--; + } + } else if (cell.type === 'next-month') { + if (month === 11) { + month = 0; + year++; + } else { + month++; + } + } + + const newDate = new Date(year, month, day); + + if (this.selectionMode === 'range') { + // todo + } else { + this.$emit('on-pick', newDate); + } + } }, handleMouseMove () { diff --git a/src/components/date-picker/base/month-table.vue b/src/components/date-picker/base/month-table.vue index 3f88c38..18d6635 100644 --- a/src/components/date-picker/base/month-table.vue +++ b/src/components/date-picker/base/month-table.vue @@ -1,13 +1,70 @@ <template> - <div>month</div> + <div :class="classes" @click="handleClick"> + <span :class="getCellCls(cell)" v-for="cell in cells"><em :index="$index">{{ cell.text }}月</em></span> + </div> </template> <script> + import { deepCopy } from '../../../utils/assist'; + const prefixCls = 'ivu-date-picker-cells'; + export default { -// props: {}, - data () { - return {} + props: { + date: {}, + month: { + type: Number + }, + disabledDate: {} }, - computed: {}, - methods: {} + computed: { + classes () { + return [ + `${prefixCls}`, + `${prefixCls}-month` + ] + }, + cells () { + let cells = []; + const cell_tmpl = { + text: '', + selected: false, + disabled: false + }; + + for (let i = 0; i < 12; i++) { + const cell = deepCopy(cell_tmpl); + cell.text = i + 1; + + const date = new Date(this.date); + date.setMonth(i); + cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(date); + + cell.selected = Number(this.month) === i; + cells.push(cell); + } + + return cells; + } + }, + methods: { + getCellCls (cell) { + return [ + `${prefixCls}-cell`, + { + [`${prefixCls}-cell-selected`]: cell.selected, + [`${prefixCls}-cell-disabled`]: cell.disabled + } + ] + }, + handleClick (event) { + const target = event.target; + if (target.tagName === 'EM') { + const index = parseInt(event.target.getAttribute('index')); + const cell = this.cells[index]; + if (cell.disabled) return; + + this.$emit('on-pick', index); + } + } + } } </script> \ No newline at end of file diff --git a/src/components/date-picker/base/year-table.vue b/src/components/date-picker/base/year-table.vue index 3e04a67..47b551e 100644 --- a/src/components/date-picker/base/year-table.vue +++ b/src/components/date-picker/base/year-table.vue @@ -1,13 +1,75 @@ <template> - <div>year</div> + <div :class="classes" @click="handleClick"> + <span :class="getCellCls(cell)" v-for="cell in cells"><em :index="$index">{{ cell.text }}</em></span> + </div> </template> <script> + import { deepCopy } from '../../../utils/assist'; + const prefixCls = 'ivu-date-picker-cells'; + export default { -// props: {}, - data () { - return {} + props: { + date: {}, + year: {}, + disabledDate: {} }, - computed: {}, - methods: {} + computed: { + classes () { + return [ + `${prefixCls}`, + `${prefixCls}-year` + ] + }, + startYear() { + return Math.floor(this.year / 10) * 10; + }, + cells () { + let cells = []; + const cell_tmpl = { + text: '', + selected: false, + disabled: false + }; + + for (let i = 0; i < 10; i++) { + const cell = deepCopy(cell_tmpl); + cell.text = this.startYear + i; + + const date = new Date(this.date); + date.setFullYear(cell.text); + cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(date); + + cell.selected = Number(this.year) === cell.text; + cells.push(cell); + } + + return cells; + } + }, + methods: { + getCellCls (cell) { + return [ + `${prefixCls}-cell`, + { + [`${prefixCls}-cell-selected`]: cell.selected, + [`${prefixCls}-cell-disabled`]: cell.disabled + } + ] + }, + nextTenYear() { + this.$emit('on-pick', Number(this.year) + 10, false); + }, + prevTenYear() { + this.$emit('on-pick', Number(this.year) - 10, false); + }, + handleClick (event) { + const target = event.target; + if (target.tagName === 'EM') { + const cell = this.cells[parseInt(event.target.getAttribute('index'))]; + if (cell.disabled) return; + this.$emit('on-pick', cell.text); + } + } + } } </script> \ No newline at end of file diff --git a/src/components/date-picker/panel/date.vue b/src/components/date-picker/panel/date.vue index fd971d2..e1c5bd2 100644 --- a/src/components/date-picker/panel/date.vue +++ b/src/components/date-picker/panel/date.vue @@ -10,25 +10,25 @@ <div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'"> <span :class="iconBtnCls('prev', '-double')" - @click="prevYear"></span> + @click="prevYear"><Icon type="ios-arrow-left"></Icon></span> <span :class="iconBtnCls('prev')" @click="prevMonth" - v-show="currentView === 'date'"></span> + v-show="currentView === 'date'"><Icon type="ios-arrow-left"></Icon></span> <span :class="[datePrefixCls + '-header-label']" - @click="showYearPicker">{{ }}</span> + @click="showYearPicker">{{ yearLabel }}</span> <span :class="[datePrefixCls + '-header-label']" @click="showMonthPicker" - v-show="currentView === 'date'">{{ }}</span> + v-show="currentView === 'date'">{{ month + 1 + '月' }}</span> + <span + :class="iconBtnCls('next', '-double')" + @click="nextYear"><Icon type="ios-arrow-right"></Icon></span> <span :class="iconBtnCls('next')" @click="nextMonth" - v-show="currentView === 'date'"></span> - <span - :class="iconBtnCls('next', '-double')" - @click="nextYear"></span> + v-show="currentView === 'date'"><Icon type="ios-arrow-right"></Icon></span> </div> <div :class="[prefixCls + '-content']"> <date-table @@ -39,25 +39,38 @@ :value="value" :week="week" :selection-mode="selectionMode" - :disabled-date="disabledDate"></date-table> + :disabled-date="disabledDate" + @on-pick="handleDatePick"></date-table> <year-table - v-show="currentView === 'year'"></year-table> + v-ref:year-table + v-show="currentView === 'year'" + :year="year" + :date="date" + :disabled-date="disabledDate" + @on-pick="handleYearPick"></year-table> <month-table - v-show="currentView === 'month'"></month-table> + v-ref:month-table + v-show="currentView === 'month'" + :month="month" + :date="date" + :disabled-date="disabledDate" + @on-pick="handleMonthPick"></month-table> </div> </div> </div> </template> <script> + import Icon from '../../icon/icon.vue'; import DateTable from '../base/date-table.vue'; import YearTable from '../base/year-table.vue'; import MonthTable from '../base/month-table.vue'; + import { formatDate, parseDate } from '../util'; const prefixCls = 'ivu-picker-panel'; const datePrefixCls = 'ivu-date-picker'; export default { - components: { DateTable, YearTable, MonthTable }, + components: { Icon, DateTable, YearTable, MonthTable }, data () { return { prefixCls: prefixCls, @@ -78,10 +91,20 @@ } }, computed: { - + yearLabel () { + const year = this.year; + if (!year) return ''; + if (this.currentView === 'year') { + const startYear = Math.floor(year / 10) * 10; + return `${startYear}年 - ${startYear + 9}年`; + } + return `${year}年` + } }, watch: { value (newVal) { + console.log(12331) + if (!newVal) return; newVal = new Date(newVal); if (!isNaN(newVal)) { // todo @@ -95,6 +118,10 @@ } }, methods: { + handleClear() { + this.date = new Date(); + this.$emit('on-pick', ''); + }, handleShortcutClick (shortcut) { }, @@ -105,23 +132,98 @@ `${datePrefixCls}-${direction}-btn-arrow${type}`, ] }, + resetDate () { + this.date = new Date(this.date); + }, prevYear () { - + if (this.currentView === 'year') { + this.$refs.yearTable.prevTenYear(); + } else { + this.year--; + this.date.setFullYear(this.year); + this.resetDate(); + } }, nextYear () { - + if (this.currentView === 'year') { + this.$refs.yearTable.nextTenYear(); + } else { + this.year++; + this.date.setFullYear(this.year); + this.resetDate(); + } }, prevMonth () { - + this.month--; + if (this.month < 0) { + this.month = 11; + this.year--; + } }, nextMonth () { - + this.month++; + if (this.month > 11) { + this.month = 0; + this.year++; + } }, showYearPicker () { - + this.currentView = 'year'; }, showMonthPicker () { + this.currentView = 'month'; + }, + handleYearPick(year, close = true) { + this.year = year; + if (!close) return; + this.date.setFullYear(year); + if (this.selectionMode === 'year') { + this.$emit('on-pick', new Date(year)); + } else { + this.currentView = 'month'; + } + + this.resetDate(); + }, + handleMonthPick (month) { + this.month = month; + const selectionMode = this.selectionMode; + if (selectionMode !== 'month') { + this.date.setMonth(month); + this.currentView = 'date'; + this.resetDate(); + } else { + this.date.setMonth(month); + this.year && this.date.setFullYear(this.year); + this.resetDate(); + const value = new Date(this.date.getFullYear(), month, 1); + this.$emit('on-pick', value); + } + }, + handleDatePick (value) { + if (this.selectionMode === 'day') { + if (!this.showTime) { + this.$emit('on-pick', new Date(value.getTime())); + } + this.date.setFullYear(value.getFullYear()); + this.date.setMonth(value.getMonth()); + this.date.setDate(value.getDate()); + } + + this.resetDate(); + }, + resetView() { + if (this.selectionMode === 'month') { + this.currentView = 'month'; + } else if (this.selectionMode === 'year') { + this.currentView = 'year'; + } else { + this.currentView = 'date'; + } + + this.year = this.date.getFullYear(); + this.month = this.date.getMonth(); } }, compiled () { @@ -133,9 +235,6 @@ this.year = this.date.getFullYear(); this.month = this.date.getMonth(); } - }, - beforeDestroy () { - } } </script> \ No newline at end of file diff --git a/src/components/date-picker/picker.vue b/src/components/date-picker/picker.vue index 34f39e7..dc3dfb9 100644 --- a/src/components/date-picker/picker.vue +++ b/src/components/date-picker/picker.vue @@ -1,9 +1,7 @@ <template> <div :class="[prefixCls]" - v-clickoutside="handleClose" - @mouseenter="handleMouseenter" - @mouseleave="handleMouseleave"> + v-clickoutside="handleClose"> <i-input v-el:reference :class="[prefixCls + '-editor']" @@ -11,10 +9,13 @@ :disabled="disabled" :size="size" :placeholder="placeholder" - :value.sync="visualValue" + :value="visualValue" + @on-change="handleInputChange" @on-focus="handleFocus" @on-blur="handleBlur" @on-click="handleIconClick" + @mouseenter="handleInputMouseenter" + @mouseleave="handleInputMouseleave" :icon="iconType"></i-input> <Drop v-show="visible" :placement="placement" transition="slide-up" v-ref:drop> <div v-el:picker></div> @@ -27,13 +28,14 @@ import Drop from '../../components/select/dropdown.vue'; import clickoutside from '../../directives/clickoutside'; import { oneOf } from '../../utils/assist'; - import { formatDate } from './util'; + import { formatDate, parseDate } from './util'; const prefixCls = 'ivu-date-picker'; const DEFAULT_FORMATS = { date: 'yyyy-MM-dd', month: 'yyyy-MM', + year: 'yyyy', datetime: 'yyyy-MM-dd HH:mm:ss', time: 'HH:mm:ss', timerange: 'HH:mm:ss', @@ -41,6 +43,96 @@ datetimerange: 'yyyy-MM-dd HH:mm:ss' }; + const RANGE_SEPARATOR = ' - '; + + const DATE_FORMATTER = function(value, format) { + return formatDate(value, format); + }; + const DATE_PARSER = function(text, format) { + return parseDate(text, format); + }; + const RANGE_FORMATTER = function(value, format) { + if (Array.isArray(value) && value.length === 2) { + const start = value[0]; + const end = value[1]; + + if (start && end) { + return formatDate(start, format) + RANGE_SEPARATOR + formatDate(end, format); + } + } + return ''; + }; + const RANGE_PARSER = function(text, format) { + const array = text.split(RANGE_SEPARATOR); + if (array.length === 2) { + const range1 = array[0]; + const range2 = array[1]; + + return [parseDate(range1, format), parseDate(range2, format)]; + } + return []; + }; + + const TYPE_VALUE_RESOLVER_MAP = { + default: { + formatter(value) { + if (!value) return ''; + return '' + value; + }, + parser(text) { + if (text === undefined || text === '') return null; + return text; + } + }, + date: { + formatter: DATE_FORMATTER, + parser: DATE_PARSER + }, + datetime: { + formatter: DATE_FORMATTER, + parser: DATE_PARSER + }, + daterange: { + formatter: RANGE_FORMATTER, + parser: RANGE_PARSER + }, + datetimerange: { + formatter: RANGE_FORMATTER, + parser: RANGE_PARSER + }, + timerange: { + formatter: RANGE_FORMATTER, + parser: RANGE_PARSER + }, + time: { + formatter: DATE_FORMATTER, + parser: DATE_PARSER + }, + month: { + formatter: DATE_FORMATTER, + parser: DATE_PARSER + }, + year: { + formatter: DATE_FORMATTER, + parser: DATE_PARSER + }, + number: { + formatter(value) { + if (!value) return ''; + return '' + value; + }, + parser(text) { + let result = Number(text); + + if (!isNaN(text)) { + return result; + } else { + return null; + } + } + } + }; + const PLACEMENT_MAP = { left: 'bottom-start', center: 'bottom-center', @@ -101,6 +193,47 @@ }, placement () { return PLACEMENT_MAP[this.align]; + }, + selectionMode() { + if (this.type === 'week') { + return 'week'; + } else if (this.type === 'month') { + return 'month'; + } else if (this.type === 'year') { + return 'year'; + } + + return 'day'; + }, + visualValue: { + get () { + const value = this.internalValue; + if (!value) return; + const formatter = ( + TYPE_VALUE_RESOLVER_MAP[this.type] || + TYPE_VALUE_RESOLVER_MAP['default'] + ).formatter; + const format = DEFAULT_FORMATS[this.type]; + + return formatter(value, this.format || format); + }, + + set (value) { + if (value) { + const type = this.type; + const parser = ( + TYPE_VALUE_RESOLVER_MAP[type] || + TYPE_VALUE_RESOLVER_MAP['default'] + ).parser; + const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type]); + + if (parsedValue) { + if (this.picker) this.picker.value = parsedValue; + } + return; + } + if (this.picker) this.picker.value = value; + } } }, methods: { @@ -113,29 +246,51 @@ handleBlur () { }, - handleMouseenter () { + handleInputChange (val) { + this.visualValue = val; + }, + handleInputMouseenter () { if (this.readonly || this.disabled) return; if (this.visualValue) { this.showClose = true; } }, - handleMouseleave () { + handleInputMouseleave () { this.showClose = false; }, handleIconClick () { - + if (!this.showClose) return; + this.visible = false; + this.internalValue = ''; + this.value = ''; }, showPicker () { if (!this.picker) { this.picker = new Vue(this.panel).$mount(this.$els.picker); this.picker.value = this.internalValue; + this.picker.selectionMode = this.selectionMode; if (this.format) this.picker.format = this.format; const options = this.options; for (const option in options) { this.picker[option] = options[option]; } + + this.picker.$on('on-pick', (date, visible = false) => { + this.$emit('on-change', date); + this.value = date; + this.visible = visible; + this.picker.resetView && this.picker.resetView(); + }); + + // todo $on('on-range') } + if (this.internalValue instanceof Date) { + this.picker.date = new Date(this.internalValue.getTime()); + } else { + this.picker.value = this.internalValue; + } + this.picker.resetView && this.picker.resetView(); } }, watch: { @@ -143,14 +298,24 @@ if (val) { this.showPicker(); this.$refs.drop.update(); + this.$emit('on-open-change', true); } else { + if (this.picker) { + this.picker.resetView && this.picker.resetView(); + } this.$refs.drop.destroy(); + this.$emit('on-open-change', false); + } + }, + internalValue(val) { + if (!val && this.picker && typeof this.picker.handleClear === 'function') { + this.picker.handleClear(); } }, value: { immediate: true, handler (val) { - this.internalValue = formatDate(val); + this.internalValue = val; } } }, diff --git a/src/components/date-picker/picker/date-picker.js b/src/components/date-picker/picker/date-picker.js index 79e77e3..7b5b8c7 100644 --- a/src/components/date-picker/picker/date-picker.js +++ b/src/components/date-picker/picker/date-picker.js @@ -20,9 +20,7 @@ export default { }, default: 'date' }, - value: { - type: [String, Array] - } + value: {} }, created () { if (!this.value) { diff --git a/src/components/input/input.vue b/src/components/input/input.vue index 39fe201..f4194ab 100644 --- a/src/components/input/input.vue +++ b/src/components/input/input.vue @@ -14,7 +14,8 @@ v-model="value" @keyup.enter="handleEnter" @focus="handleFocus" - @blur="handleBlur"> + @blur="handleBlur" + @change="handleChange"> <div :class="[prefixCls + '-group-append']" v-if="append" v-show="slotReady" v-el:append><slot name="append"></slot></div> </template> <textarea @@ -31,7 +32,8 @@ v-model="value" @keyup.enter="handleEnter" @focus="handleFocus" - @blur="handleBlur"> + @blur="handleBlur" + @change="handleChange"> </textarea> </div> </template> @@ -52,7 +54,7 @@ value: { type: [String, Number], default: '', - twoWay: true +// twoWay: true }, size: { validator (value) { @@ -139,6 +141,9 @@ handleBlur () { this.$emit('on-blur'); }, + handleChange () { + this.$emit('on-change', this.value); + }, resizeTextarea () { const autosize = this.autosize; if (!autosize || this.type !== 'textarea') { @@ -152,11 +157,10 @@ } }, watch: { - value (val) { + value () { this.$nextTick(() => { this.resizeTextarea(); }); - this.$emit('on-change', val); } }, ready () { diff --git a/src/styles/components/date-picker.less b/src/styles/components/date-picker.less index ff8adb0..2015a10 100644 --- a/src/styles/components/date-picker.less +++ b/src/styles/components/date-picker.less @@ -1,9 +1,11 @@ @date-picker-prefix-cls: ~"@{css-prefix}date-picker"; +@picker-prefix-cls: ~"@{css-prefix}picker"; .@{date-picker-prefix-cls} { position: relative; .@{select-dropdown-prefix-cls} { width: auto; + padding: 0; overflow: visible; max-height: none; } @@ -100,4 +102,75 @@ } } } + + &-cells-year,&-cells-month{ + margin-top: 14px; + span{ + width: 40px; + height: 28px; + line-height: 28px; + margin: 10px 12px; + border-radius: @btn-border-radius-small; + em{ + width: 40px; + height: 28px; + line-height: 28px; + margin: 0; + } + } + } + + &-header{ + height: 32px; + line-height: 32px; + text-align: center; + border-bottom: 1px solid @border-color-split; + &-label{ + cursor: pointer; + transition: color @transition-time @ease-in-out; + &:hover{ + color: @primary-color; + } + } + } + &-prev-btn{ + float: left; + &-arrow-double{ + margin-left: 10px; + i:after{ + content: "\F3D2"; + } + } + } + &-next-btn{ + float: right; + &-arrow-double{ + margin-right: 10px; + i:after{ + content: "\F3D3"; + } + } + } +} + +.@{picker-prefix-cls} { + &-panel{ + &-icon-btn{ + display: inline-block; + width: 20px; + height: 24px; + line-height: 26px; + margin-top: 4px; + text-align: center; + cursor: pointer; + color: @btn-disable-color; + transition: color @transition-time @ease-in-out; + &:hover{ + color: @primary-color; + } + i{ + font-size: 14px; + } + } + } } \ No newline at end of file diff --git a/test/routers/date.vue b/test/routers/date.vue index cb599a1..73618ab 100644 --- a/test/routers/date.vue +++ b/test/routers/date.vue @@ -1,23 +1,42 @@ <template> <div style="margin: 50px"> - <date-picker style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options"></date-picker> + <br> + <row> + <i-col span="4"> + <date-picker style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options" @on-change="change" @on-open-change="change2" :format="format"></date-picker> + </i-col> + <i-col span="4"> + <date-picker type="year" style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options"></date-picker> + </i-col> + <i-col span="4"> + <date-picker type="month" style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options"></date-picker> + </i-col> + </row> </div> </template> <script> export default { data () { return { - value: '2016-12-18', + value: new Date(), // value: '', options: { disabledDate(time) { return time.getTime() < Date.now() - 8.64e7; // return time && time.valueOf() < Date.now(); } - } + }, + format: 'yyyy/MM/dd' } }, computed: {}, - methods: {} + methods: { + change (date) { +// console.log(date) + }, + change2 (s) { +// console.log(s) + } + } } </script> \ No newline at end of file -- libgit2 0.21.4