Commit c46f385a83c9bca751bed6a7d764d4a88dee3573
1 parent
10f622ac
update DatePicker
update DatePicker
Showing
9 changed files
with
565 additions
and
56 deletions
Show diff stats
src/components/date-picker/base/date-table.vue
| ... | ... | @@ -6,7 +6,7 @@ |
| 6 | 6 | <div :class="[prefixCls + '-header']"> |
| 7 | 7 | <span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span> |
| 8 | 8 | </div> |
| 9 | - <span :class="getCellCls(cell)" v-for="cell in cells"><em>{{ cell.text }}</em></span> | |
| 9 | + <span :class="getCellCls(cell)" v-for="cell in cells"><em :index="$index">{{ cell.text }}</em></span> | |
| 10 | 10 | </div> |
| 11 | 11 | </template> |
| 12 | 12 | <script> |
| ... | ... | @@ -126,8 +126,40 @@ |
| 126 | 126 | } |
| 127 | 127 | }, |
| 128 | 128 | methods: { |
| 129 | - handleClick () { | |
| 129 | + handleClick (event) { | |
| 130 | + const target = event.target; | |
| 131 | + if (target.tagName === 'EM') { | |
| 132 | + const cell = this.cells[parseInt(event.target.getAttribute('index'))]; | |
| 133 | + if (cell.disabled) return; | |
| 130 | 134 | |
| 135 | + let year = this.year; | |
| 136 | + let month = this.month; | |
| 137 | + let day = cell.text; | |
| 138 | + | |
| 139 | + if (cell.type === 'prev-month') { | |
| 140 | + if (month === 0) { | |
| 141 | + month = 11; | |
| 142 | + year--; | |
| 143 | + } else { | |
| 144 | + month--; | |
| 145 | + } | |
| 146 | + } else if (cell.type === 'next-month') { | |
| 147 | + if (month === 11) { | |
| 148 | + month = 0; | |
| 149 | + year++; | |
| 150 | + } else { | |
| 151 | + month++; | |
| 152 | + } | |
| 153 | + } | |
| 154 | + | |
| 155 | + const newDate = new Date(year, month, day); | |
| 156 | + | |
| 157 | + if (this.selectionMode === 'range') { | |
| 158 | + // todo | |
| 159 | + } else { | |
| 160 | + this.$emit('on-pick', newDate); | |
| 161 | + } | |
| 162 | + } | |
| 131 | 163 | }, |
| 132 | 164 | handleMouseMove () { |
| 133 | 165 | ... | ... |
src/components/date-picker/base/month-table.vue
| 1 | 1 | <template> |
| 2 | - <div>month</div> | |
| 2 | + <div :class="classes" @click="handleClick"> | |
| 3 | + <span :class="getCellCls(cell)" v-for="cell in cells"><em :index="$index">{{ cell.text }}月</em></span> | |
| 4 | + </div> | |
| 3 | 5 | </template> |
| 4 | 6 | <script> |
| 7 | + import { deepCopy } from '../../../utils/assist'; | |
| 8 | + const prefixCls = 'ivu-date-picker-cells'; | |
| 9 | + | |
| 5 | 10 | export default { |
| 6 | -// props: {}, | |
| 7 | - data () { | |
| 8 | - return {} | |
| 11 | + props: { | |
| 12 | + date: {}, | |
| 13 | + month: { | |
| 14 | + type: Number | |
| 15 | + }, | |
| 16 | + disabledDate: {} | |
| 9 | 17 | }, |
| 10 | - computed: {}, | |
| 11 | - methods: {} | |
| 18 | + computed: { | |
| 19 | + classes () { | |
| 20 | + return [ | |
| 21 | + `${prefixCls}`, | |
| 22 | + `${prefixCls}-month` | |
| 23 | + ] | |
| 24 | + }, | |
| 25 | + cells () { | |
| 26 | + let cells = []; | |
| 27 | + const cell_tmpl = { | |
| 28 | + text: '', | |
| 29 | + selected: false, | |
| 30 | + disabled: false | |
| 31 | + }; | |
| 32 | + | |
| 33 | + for (let i = 0; i < 12; i++) { | |
| 34 | + const cell = deepCopy(cell_tmpl); | |
| 35 | + cell.text = i + 1; | |
| 36 | + | |
| 37 | + const date = new Date(this.date); | |
| 38 | + date.setMonth(i); | |
| 39 | + cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(date); | |
| 40 | + | |
| 41 | + cell.selected = Number(this.month) === i; | |
| 42 | + cells.push(cell); | |
| 43 | + } | |
| 44 | + | |
| 45 | + return cells; | |
| 46 | + } | |
| 47 | + }, | |
| 48 | + methods: { | |
| 49 | + getCellCls (cell) { | |
| 50 | + return [ | |
| 51 | + `${prefixCls}-cell`, | |
| 52 | + { | |
| 53 | + [`${prefixCls}-cell-selected`]: cell.selected, | |
| 54 | + [`${prefixCls}-cell-disabled`]: cell.disabled | |
| 55 | + } | |
| 56 | + ] | |
| 57 | + }, | |
| 58 | + handleClick (event) { | |
| 59 | + const target = event.target; | |
| 60 | + if (target.tagName === 'EM') { | |
| 61 | + const index = parseInt(event.target.getAttribute('index')); | |
| 62 | + const cell = this.cells[index]; | |
| 63 | + if (cell.disabled) return; | |
| 64 | + | |
| 65 | + this.$emit('on-pick', index); | |
| 66 | + } | |
| 67 | + } | |
| 68 | + } | |
| 12 | 69 | } |
| 13 | 70 | </script> |
| 14 | 71 | \ No newline at end of file | ... | ... |
src/components/date-picker/base/year-table.vue
| 1 | 1 | <template> |
| 2 | - <div>year</div> | |
| 2 | + <div :class="classes" @click="handleClick"> | |
| 3 | + <span :class="getCellCls(cell)" v-for="cell in cells"><em :index="$index">{{ cell.text }}</em></span> | |
| 4 | + </div> | |
| 3 | 5 | </template> |
| 4 | 6 | <script> |
| 7 | + import { deepCopy } from '../../../utils/assist'; | |
| 8 | + const prefixCls = 'ivu-date-picker-cells'; | |
| 9 | + | |
| 5 | 10 | export default { |
| 6 | -// props: {}, | |
| 7 | - data () { | |
| 8 | - return {} | |
| 11 | + props: { | |
| 12 | + date: {}, | |
| 13 | + year: {}, | |
| 14 | + disabledDate: {} | |
| 9 | 15 | }, |
| 10 | - computed: {}, | |
| 11 | - methods: {} | |
| 16 | + computed: { | |
| 17 | + classes () { | |
| 18 | + return [ | |
| 19 | + `${prefixCls}`, | |
| 20 | + `${prefixCls}-year` | |
| 21 | + ] | |
| 22 | + }, | |
| 23 | + startYear() { | |
| 24 | + return Math.floor(this.year / 10) * 10; | |
| 25 | + }, | |
| 26 | + cells () { | |
| 27 | + let cells = []; | |
| 28 | + const cell_tmpl = { | |
| 29 | + text: '', | |
| 30 | + selected: false, | |
| 31 | + disabled: false | |
| 32 | + }; | |
| 33 | + | |
| 34 | + for (let i = 0; i < 10; i++) { | |
| 35 | + const cell = deepCopy(cell_tmpl); | |
| 36 | + cell.text = this.startYear + i; | |
| 37 | + | |
| 38 | + const date = new Date(this.date); | |
| 39 | + date.setFullYear(cell.text); | |
| 40 | + cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(date); | |
| 41 | + | |
| 42 | + cell.selected = Number(this.year) === cell.text; | |
| 43 | + cells.push(cell); | |
| 44 | + } | |
| 45 | + | |
| 46 | + return cells; | |
| 47 | + } | |
| 48 | + }, | |
| 49 | + methods: { | |
| 50 | + getCellCls (cell) { | |
| 51 | + return [ | |
| 52 | + `${prefixCls}-cell`, | |
| 53 | + { | |
| 54 | + [`${prefixCls}-cell-selected`]: cell.selected, | |
| 55 | + [`${prefixCls}-cell-disabled`]: cell.disabled | |
| 56 | + } | |
| 57 | + ] | |
| 58 | + }, | |
| 59 | + nextTenYear() { | |
| 60 | + this.$emit('on-pick', Number(this.year) + 10, false); | |
| 61 | + }, | |
| 62 | + prevTenYear() { | |
| 63 | + this.$emit('on-pick', Number(this.year) - 10, false); | |
| 64 | + }, | |
| 65 | + handleClick (event) { | |
| 66 | + const target = event.target; | |
| 67 | + if (target.tagName === 'EM') { | |
| 68 | + const cell = this.cells[parseInt(event.target.getAttribute('index'))]; | |
| 69 | + if (cell.disabled) return; | |
| 70 | + this.$emit('on-pick', cell.text); | |
| 71 | + } | |
| 72 | + } | |
| 73 | + } | |
| 12 | 74 | } |
| 13 | 75 | </script> |
| 14 | 76 | \ No newline at end of file | ... | ... |
src/components/date-picker/panel/date.vue
| ... | ... | @@ -10,25 +10,25 @@ |
| 10 | 10 | <div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'"> |
| 11 | 11 | <span |
| 12 | 12 | :class="iconBtnCls('prev', '-double')" |
| 13 | - @click="prevYear"></span> | |
| 13 | + @click="prevYear"><Icon type="ios-arrow-left"></Icon></span> | |
| 14 | 14 | <span |
| 15 | 15 | :class="iconBtnCls('prev')" |
| 16 | 16 | @click="prevMonth" |
| 17 | - v-show="currentView === 'date'"></span> | |
| 17 | + v-show="currentView === 'date'"><Icon type="ios-arrow-left"></Icon></span> | |
| 18 | 18 | <span |
| 19 | 19 | :class="[datePrefixCls + '-header-label']" |
| 20 | - @click="showYearPicker">{{ }}</span> | |
| 20 | + @click="showYearPicker">{{ yearLabel }}</span> | |
| 21 | 21 | <span |
| 22 | 22 | :class="[datePrefixCls + '-header-label']" |
| 23 | 23 | @click="showMonthPicker" |
| 24 | - v-show="currentView === 'date'">{{ }}</span> | |
| 24 | + v-show="currentView === 'date'">{{ month + 1 + '月' }}</span> | |
| 25 | + <span | |
| 26 | + :class="iconBtnCls('next', '-double')" | |
| 27 | + @click="nextYear"><Icon type="ios-arrow-right"></Icon></span> | |
| 25 | 28 | <span |
| 26 | 29 | :class="iconBtnCls('next')" |
| 27 | 30 | @click="nextMonth" |
| 28 | - v-show="currentView === 'date'"></span> | |
| 29 | - <span | |
| 30 | - :class="iconBtnCls('next', '-double')" | |
| 31 | - @click="nextYear"></span> | |
| 31 | + v-show="currentView === 'date'"><Icon type="ios-arrow-right"></Icon></span> | |
| 32 | 32 | </div> |
| 33 | 33 | <div :class="[prefixCls + '-content']"> |
| 34 | 34 | <date-table |
| ... | ... | @@ -39,25 +39,38 @@ |
| 39 | 39 | :value="value" |
| 40 | 40 | :week="week" |
| 41 | 41 | :selection-mode="selectionMode" |
| 42 | - :disabled-date="disabledDate"></date-table> | |
| 42 | + :disabled-date="disabledDate" | |
| 43 | + @on-pick="handleDatePick"></date-table> | |
| 43 | 44 | <year-table |
| 44 | - v-show="currentView === 'year'"></year-table> | |
| 45 | + v-ref:year-table | |
| 46 | + v-show="currentView === 'year'" | |
| 47 | + :year="year" | |
| 48 | + :date="date" | |
| 49 | + :disabled-date="disabledDate" | |
| 50 | + @on-pick="handleYearPick"></year-table> | |
| 45 | 51 | <month-table |
| 46 | - v-show="currentView === 'month'"></month-table> | |
| 52 | + v-ref:month-table | |
| 53 | + v-show="currentView === 'month'" | |
| 54 | + :month="month" | |
| 55 | + :date="date" | |
| 56 | + :disabled-date="disabledDate" | |
| 57 | + @on-pick="handleMonthPick"></month-table> | |
| 47 | 58 | </div> |
| 48 | 59 | </div> |
| 49 | 60 | </div> |
| 50 | 61 | </template> |
| 51 | 62 | <script> |
| 63 | + import Icon from '../../icon/icon.vue'; | |
| 52 | 64 | import DateTable from '../base/date-table.vue'; |
| 53 | 65 | import YearTable from '../base/year-table.vue'; |
| 54 | 66 | import MonthTable from '../base/month-table.vue'; |
| 67 | + import { formatDate, parseDate } from '../util'; | |
| 55 | 68 | |
| 56 | 69 | const prefixCls = 'ivu-picker-panel'; |
| 57 | 70 | const datePrefixCls = 'ivu-date-picker'; |
| 58 | 71 | |
| 59 | 72 | export default { |
| 60 | - components: { DateTable, YearTable, MonthTable }, | |
| 73 | + components: { Icon, DateTable, YearTable, MonthTable }, | |
| 61 | 74 | data () { |
| 62 | 75 | return { |
| 63 | 76 | prefixCls: prefixCls, |
| ... | ... | @@ -78,10 +91,20 @@ |
| 78 | 91 | } |
| 79 | 92 | }, |
| 80 | 93 | computed: { |
| 81 | - | |
| 94 | + yearLabel () { | |
| 95 | + const year = this.year; | |
| 96 | + if (!year) return ''; | |
| 97 | + if (this.currentView === 'year') { | |
| 98 | + const startYear = Math.floor(year / 10) * 10; | |
| 99 | + return `${startYear}年 - ${startYear + 9}年`; | |
| 100 | + } | |
| 101 | + return `${year}年` | |
| 102 | + } | |
| 82 | 103 | }, |
| 83 | 104 | watch: { |
| 84 | 105 | value (newVal) { |
| 106 | + console.log(12331) | |
| 107 | + if (!newVal) return; | |
| 85 | 108 | newVal = new Date(newVal); |
| 86 | 109 | if (!isNaN(newVal)) { |
| 87 | 110 | // todo |
| ... | ... | @@ -95,6 +118,10 @@ |
| 95 | 118 | } |
| 96 | 119 | }, |
| 97 | 120 | methods: { |
| 121 | + handleClear() { | |
| 122 | + this.date = new Date(); | |
| 123 | + this.$emit('on-pick', ''); | |
| 124 | + }, | |
| 98 | 125 | handleShortcutClick (shortcut) { |
| 99 | 126 | |
| 100 | 127 | }, |
| ... | ... | @@ -105,23 +132,98 @@ |
| 105 | 132 | `${datePrefixCls}-${direction}-btn-arrow${type}`, |
| 106 | 133 | ] |
| 107 | 134 | }, |
| 135 | + resetDate () { | |
| 136 | + this.date = new Date(this.date); | |
| 137 | + }, | |
| 108 | 138 | prevYear () { |
| 109 | - | |
| 139 | + if (this.currentView === 'year') { | |
| 140 | + this.$refs.yearTable.prevTenYear(); | |
| 141 | + } else { | |
| 142 | + this.year--; | |
| 143 | + this.date.setFullYear(this.year); | |
| 144 | + this.resetDate(); | |
| 145 | + } | |
| 110 | 146 | }, |
| 111 | 147 | nextYear () { |
| 112 | - | |
| 148 | + if (this.currentView === 'year') { | |
| 149 | + this.$refs.yearTable.nextTenYear(); | |
| 150 | + } else { | |
| 151 | + this.year++; | |
| 152 | + this.date.setFullYear(this.year); | |
| 153 | + this.resetDate(); | |
| 154 | + } | |
| 113 | 155 | }, |
| 114 | 156 | prevMonth () { |
| 115 | - | |
| 157 | + this.month--; | |
| 158 | + if (this.month < 0) { | |
| 159 | + this.month = 11; | |
| 160 | + this.year--; | |
| 161 | + } | |
| 116 | 162 | }, |
| 117 | 163 | nextMonth () { |
| 118 | - | |
| 164 | + this.month++; | |
| 165 | + if (this.month > 11) { | |
| 166 | + this.month = 0; | |
| 167 | + this.year++; | |
| 168 | + } | |
| 119 | 169 | }, |
| 120 | 170 | showYearPicker () { |
| 121 | - | |
| 171 | + this.currentView = 'year'; | |
| 122 | 172 | }, |
| 123 | 173 | showMonthPicker () { |
| 174 | + this.currentView = 'month'; | |
| 175 | + }, | |
| 176 | + handleYearPick(year, close = true) { | |
| 177 | + this.year = year; | |
| 178 | + if (!close) return; | |
| 124 | 179 | |
| 180 | + this.date.setFullYear(year); | |
| 181 | + if (this.selectionMode === 'year') { | |
| 182 | + this.$emit('on-pick', new Date(year)); | |
| 183 | + } else { | |
| 184 | + this.currentView = 'month'; | |
| 185 | + } | |
| 186 | + | |
| 187 | + this.resetDate(); | |
| 188 | + }, | |
| 189 | + handleMonthPick (month) { | |
| 190 | + this.month = month; | |
| 191 | + const selectionMode = this.selectionMode; | |
| 192 | + if (selectionMode !== 'month') { | |
| 193 | + this.date.setMonth(month); | |
| 194 | + this.currentView = 'date'; | |
| 195 | + this.resetDate(); | |
| 196 | + } else { | |
| 197 | + this.date.setMonth(month); | |
| 198 | + this.year && this.date.setFullYear(this.year); | |
| 199 | + this.resetDate(); | |
| 200 | + const value = new Date(this.date.getFullYear(), month, 1); | |
| 201 | + this.$emit('on-pick', value); | |
| 202 | + } | |
| 203 | + }, | |
| 204 | + handleDatePick (value) { | |
| 205 | + if (this.selectionMode === 'day') { | |
| 206 | + if (!this.showTime) { | |
| 207 | + this.$emit('on-pick', new Date(value.getTime())); | |
| 208 | + } | |
| 209 | + this.date.setFullYear(value.getFullYear()); | |
| 210 | + this.date.setMonth(value.getMonth()); | |
| 211 | + this.date.setDate(value.getDate()); | |
| 212 | + } | |
| 213 | + | |
| 214 | + this.resetDate(); | |
| 215 | + }, | |
| 216 | + resetView() { | |
| 217 | + if (this.selectionMode === 'month') { | |
| 218 | + this.currentView = 'month'; | |
| 219 | + } else if (this.selectionMode === 'year') { | |
| 220 | + this.currentView = 'year'; | |
| 221 | + } else { | |
| 222 | + this.currentView = 'date'; | |
| 223 | + } | |
| 224 | + | |
| 225 | + this.year = this.date.getFullYear(); | |
| 226 | + this.month = this.date.getMonth(); | |
| 125 | 227 | } |
| 126 | 228 | }, |
| 127 | 229 | compiled () { |
| ... | ... | @@ -133,9 +235,6 @@ |
| 133 | 235 | this.year = this.date.getFullYear(); |
| 134 | 236 | this.month = this.date.getMonth(); |
| 135 | 237 | } |
| 136 | - }, | |
| 137 | - beforeDestroy () { | |
| 138 | - | |
| 139 | 238 | } |
| 140 | 239 | } |
| 141 | 240 | </script> |
| 142 | 241 | \ No newline at end of file | ... | ... |
src/components/date-picker/picker.vue
| 1 | 1 | <template> |
| 2 | 2 | <div |
| 3 | 3 | :class="[prefixCls]" |
| 4 | - v-clickoutside="handleClose" | |
| 5 | - @mouseenter="handleMouseenter" | |
| 6 | - @mouseleave="handleMouseleave"> | |
| 4 | + v-clickoutside="handleClose"> | |
| 7 | 5 | <i-input |
| 8 | 6 | v-el:reference |
| 9 | 7 | :class="[prefixCls + '-editor']" |
| ... | ... | @@ -11,10 +9,13 @@ |
| 11 | 9 | :disabled="disabled" |
| 12 | 10 | :size="size" |
| 13 | 11 | :placeholder="placeholder" |
| 14 | - :value.sync="visualValue" | |
| 12 | + :value="visualValue" | |
| 13 | + @on-change="handleInputChange" | |
| 15 | 14 | @on-focus="handleFocus" |
| 16 | 15 | @on-blur="handleBlur" |
| 17 | 16 | @on-click="handleIconClick" |
| 17 | + @mouseenter="handleInputMouseenter" | |
| 18 | + @mouseleave="handleInputMouseleave" | |
| 18 | 19 | :icon="iconType"></i-input> |
| 19 | 20 | <Drop v-show="visible" :placement="placement" transition="slide-up" v-ref:drop> |
| 20 | 21 | <div v-el:picker></div> |
| ... | ... | @@ -27,13 +28,14 @@ |
| 27 | 28 | import Drop from '../../components/select/dropdown.vue'; |
| 28 | 29 | import clickoutside from '../../directives/clickoutside'; |
| 29 | 30 | import { oneOf } from '../../utils/assist'; |
| 30 | - import { formatDate } from './util'; | |
| 31 | + import { formatDate, parseDate } from './util'; | |
| 31 | 32 | |
| 32 | 33 | const prefixCls = 'ivu-date-picker'; |
| 33 | 34 | |
| 34 | 35 | const DEFAULT_FORMATS = { |
| 35 | 36 | date: 'yyyy-MM-dd', |
| 36 | 37 | month: 'yyyy-MM', |
| 38 | + year: 'yyyy', | |
| 37 | 39 | datetime: 'yyyy-MM-dd HH:mm:ss', |
| 38 | 40 | time: 'HH:mm:ss', |
| 39 | 41 | timerange: 'HH:mm:ss', |
| ... | ... | @@ -41,6 +43,96 @@ |
| 41 | 43 | datetimerange: 'yyyy-MM-dd HH:mm:ss' |
| 42 | 44 | }; |
| 43 | 45 | |
| 46 | + const RANGE_SEPARATOR = ' - '; | |
| 47 | + | |
| 48 | + const DATE_FORMATTER = function(value, format) { | |
| 49 | + return formatDate(value, format); | |
| 50 | + }; | |
| 51 | + const DATE_PARSER = function(text, format) { | |
| 52 | + return parseDate(text, format); | |
| 53 | + }; | |
| 54 | + const RANGE_FORMATTER = function(value, format) { | |
| 55 | + if (Array.isArray(value) && value.length === 2) { | |
| 56 | + const start = value[0]; | |
| 57 | + const end = value[1]; | |
| 58 | + | |
| 59 | + if (start && end) { | |
| 60 | + return formatDate(start, format) + RANGE_SEPARATOR + formatDate(end, format); | |
| 61 | + } | |
| 62 | + } | |
| 63 | + return ''; | |
| 64 | + }; | |
| 65 | + const RANGE_PARSER = function(text, format) { | |
| 66 | + const array = text.split(RANGE_SEPARATOR); | |
| 67 | + if (array.length === 2) { | |
| 68 | + const range1 = array[0]; | |
| 69 | + const range2 = array[1]; | |
| 70 | + | |
| 71 | + return [parseDate(range1, format), parseDate(range2, format)]; | |
| 72 | + } | |
| 73 | + return []; | |
| 74 | + }; | |
| 75 | + | |
| 76 | + const TYPE_VALUE_RESOLVER_MAP = { | |
| 77 | + default: { | |
| 78 | + formatter(value) { | |
| 79 | + if (!value) return ''; | |
| 80 | + return '' + value; | |
| 81 | + }, | |
| 82 | + parser(text) { | |
| 83 | + if (text === undefined || text === '') return null; | |
| 84 | + return text; | |
| 85 | + } | |
| 86 | + }, | |
| 87 | + date: { | |
| 88 | + formatter: DATE_FORMATTER, | |
| 89 | + parser: DATE_PARSER | |
| 90 | + }, | |
| 91 | + datetime: { | |
| 92 | + formatter: DATE_FORMATTER, | |
| 93 | + parser: DATE_PARSER | |
| 94 | + }, | |
| 95 | + daterange: { | |
| 96 | + formatter: RANGE_FORMATTER, | |
| 97 | + parser: RANGE_PARSER | |
| 98 | + }, | |
| 99 | + datetimerange: { | |
| 100 | + formatter: RANGE_FORMATTER, | |
| 101 | + parser: RANGE_PARSER | |
| 102 | + }, | |
| 103 | + timerange: { | |
| 104 | + formatter: RANGE_FORMATTER, | |
| 105 | + parser: RANGE_PARSER | |
| 106 | + }, | |
| 107 | + time: { | |
| 108 | + formatter: DATE_FORMATTER, | |
| 109 | + parser: DATE_PARSER | |
| 110 | + }, | |
| 111 | + month: { | |
| 112 | + formatter: DATE_FORMATTER, | |
| 113 | + parser: DATE_PARSER | |
| 114 | + }, | |
| 115 | + year: { | |
| 116 | + formatter: DATE_FORMATTER, | |
| 117 | + parser: DATE_PARSER | |
| 118 | + }, | |
| 119 | + number: { | |
| 120 | + formatter(value) { | |
| 121 | + if (!value) return ''; | |
| 122 | + return '' + value; | |
| 123 | + }, | |
| 124 | + parser(text) { | |
| 125 | + let result = Number(text); | |
| 126 | + | |
| 127 | + if (!isNaN(text)) { | |
| 128 | + return result; | |
| 129 | + } else { | |
| 130 | + return null; | |
| 131 | + } | |
| 132 | + } | |
| 133 | + } | |
| 134 | + }; | |
| 135 | + | |
| 44 | 136 | const PLACEMENT_MAP = { |
| 45 | 137 | left: 'bottom-start', |
| 46 | 138 | center: 'bottom-center', |
| ... | ... | @@ -101,6 +193,47 @@ |
| 101 | 193 | }, |
| 102 | 194 | placement () { |
| 103 | 195 | return PLACEMENT_MAP[this.align]; |
| 196 | + }, | |
| 197 | + selectionMode() { | |
| 198 | + if (this.type === 'week') { | |
| 199 | + return 'week'; | |
| 200 | + } else if (this.type === 'month') { | |
| 201 | + return 'month'; | |
| 202 | + } else if (this.type === 'year') { | |
| 203 | + return 'year'; | |
| 204 | + } | |
| 205 | + | |
| 206 | + return 'day'; | |
| 207 | + }, | |
| 208 | + visualValue: { | |
| 209 | + get () { | |
| 210 | + const value = this.internalValue; | |
| 211 | + if (!value) return; | |
| 212 | + const formatter = ( | |
| 213 | + TYPE_VALUE_RESOLVER_MAP[this.type] || | |
| 214 | + TYPE_VALUE_RESOLVER_MAP['default'] | |
| 215 | + ).formatter; | |
| 216 | + const format = DEFAULT_FORMATS[this.type]; | |
| 217 | + | |
| 218 | + return formatter(value, this.format || format); | |
| 219 | + }, | |
| 220 | + | |
| 221 | + set (value) { | |
| 222 | + if (value) { | |
| 223 | + const type = this.type; | |
| 224 | + const parser = ( | |
| 225 | + TYPE_VALUE_RESOLVER_MAP[type] || | |
| 226 | + TYPE_VALUE_RESOLVER_MAP['default'] | |
| 227 | + ).parser; | |
| 228 | + const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type]); | |
| 229 | + | |
| 230 | + if (parsedValue) { | |
| 231 | + if (this.picker) this.picker.value = parsedValue; | |
| 232 | + } | |
| 233 | + return; | |
| 234 | + } | |
| 235 | + if (this.picker) this.picker.value = value; | |
| 236 | + } | |
| 104 | 237 | } |
| 105 | 238 | }, |
| 106 | 239 | methods: { |
| ... | ... | @@ -113,29 +246,51 @@ |
| 113 | 246 | handleBlur () { |
| 114 | 247 | |
| 115 | 248 | }, |
| 116 | - handleMouseenter () { | |
| 249 | + handleInputChange (val) { | |
| 250 | + this.visualValue = val; | |
| 251 | + }, | |
| 252 | + handleInputMouseenter () { | |
| 117 | 253 | if (this.readonly || this.disabled) return; |
| 118 | 254 | if (this.visualValue) { |
| 119 | 255 | this.showClose = true; |
| 120 | 256 | } |
| 121 | 257 | }, |
| 122 | - handleMouseleave () { | |
| 258 | + handleInputMouseleave () { | |
| 123 | 259 | this.showClose = false; |
| 124 | 260 | }, |
| 125 | 261 | handleIconClick () { |
| 126 | - | |
| 262 | + if (!this.showClose) return; | |
| 263 | + this.visible = false; | |
| 264 | + this.internalValue = ''; | |
| 265 | + this.value = ''; | |
| 127 | 266 | }, |
| 128 | 267 | showPicker () { |
| 129 | 268 | if (!this.picker) { |
| 130 | 269 | this.picker = new Vue(this.panel).$mount(this.$els.picker); |
| 131 | 270 | this.picker.value = this.internalValue; |
| 271 | + this.picker.selectionMode = this.selectionMode; | |
| 132 | 272 | if (this.format) this.picker.format = this.format; |
| 133 | 273 | |
| 134 | 274 | const options = this.options; |
| 135 | 275 | for (const option in options) { |
| 136 | 276 | this.picker[option] = options[option]; |
| 137 | 277 | } |
| 278 | + | |
| 279 | + this.picker.$on('on-pick', (date, visible = false) => { | |
| 280 | + this.$emit('on-change', date); | |
| 281 | + this.value = date; | |
| 282 | + this.visible = visible; | |
| 283 | + this.picker.resetView && this.picker.resetView(); | |
| 284 | + }); | |
| 285 | + | |
| 286 | + // todo $on('on-range') | |
| 138 | 287 | } |
| 288 | + if (this.internalValue instanceof Date) { | |
| 289 | + this.picker.date = new Date(this.internalValue.getTime()); | |
| 290 | + } else { | |
| 291 | + this.picker.value = this.internalValue; | |
| 292 | + } | |
| 293 | + this.picker.resetView && this.picker.resetView(); | |
| 139 | 294 | } |
| 140 | 295 | }, |
| 141 | 296 | watch: { |
| ... | ... | @@ -143,14 +298,24 @@ |
| 143 | 298 | if (val) { |
| 144 | 299 | this.showPicker(); |
| 145 | 300 | this.$refs.drop.update(); |
| 301 | + this.$emit('on-open-change', true); | |
| 146 | 302 | } else { |
| 303 | + if (this.picker) { | |
| 304 | + this.picker.resetView && this.picker.resetView(); | |
| 305 | + } | |
| 147 | 306 | this.$refs.drop.destroy(); |
| 307 | + this.$emit('on-open-change', false); | |
| 308 | + } | |
| 309 | + }, | |
| 310 | + internalValue(val) { | |
| 311 | + if (!val && this.picker && typeof this.picker.handleClear === 'function') { | |
| 312 | + this.picker.handleClear(); | |
| 148 | 313 | } |
| 149 | 314 | }, |
| 150 | 315 | value: { |
| 151 | 316 | immediate: true, |
| 152 | 317 | handler (val) { |
| 153 | - this.internalValue = formatDate(val); | |
| 318 | + this.internalValue = val; | |
| 154 | 319 | } |
| 155 | 320 | } |
| 156 | 321 | }, | ... | ... |
src/components/date-picker/picker/date-picker.js
src/components/input/input.vue
| ... | ... | @@ -14,7 +14,8 @@ |
| 14 | 14 | v-model="value" |
| 15 | 15 | @keyup.enter="handleEnter" |
| 16 | 16 | @focus="handleFocus" |
| 17 | - @blur="handleBlur"> | |
| 17 | + @blur="handleBlur" | |
| 18 | + @change="handleChange"> | |
| 18 | 19 | <div :class="[prefixCls + '-group-append']" v-if="append" v-show="slotReady" v-el:append><slot name="append"></slot></div> |
| 19 | 20 | </template> |
| 20 | 21 | <textarea |
| ... | ... | @@ -31,7 +32,8 @@ |
| 31 | 32 | v-model="value" |
| 32 | 33 | @keyup.enter="handleEnter" |
| 33 | 34 | @focus="handleFocus" |
| 34 | - @blur="handleBlur"> | |
| 35 | + @blur="handleBlur" | |
| 36 | + @change="handleChange"> | |
| 35 | 37 | </textarea> |
| 36 | 38 | </div> |
| 37 | 39 | </template> |
| ... | ... | @@ -52,7 +54,7 @@ |
| 52 | 54 | value: { |
| 53 | 55 | type: [String, Number], |
| 54 | 56 | default: '', |
| 55 | - twoWay: true | |
| 57 | +// twoWay: true | |
| 56 | 58 | }, |
| 57 | 59 | size: { |
| 58 | 60 | validator (value) { |
| ... | ... | @@ -139,6 +141,9 @@ |
| 139 | 141 | handleBlur () { |
| 140 | 142 | this.$emit('on-blur'); |
| 141 | 143 | }, |
| 144 | + handleChange () { | |
| 145 | + this.$emit('on-change', this.value); | |
| 146 | + }, | |
| 142 | 147 | resizeTextarea () { |
| 143 | 148 | const autosize = this.autosize; |
| 144 | 149 | if (!autosize || this.type !== 'textarea') { |
| ... | ... | @@ -152,11 +157,10 @@ |
| 152 | 157 | } |
| 153 | 158 | }, |
| 154 | 159 | watch: { |
| 155 | - value (val) { | |
| 160 | + value () { | |
| 156 | 161 | this.$nextTick(() => { |
| 157 | 162 | this.resizeTextarea(); |
| 158 | 163 | }); |
| 159 | - this.$emit('on-change', val); | |
| 160 | 164 | } |
| 161 | 165 | }, |
| 162 | 166 | ready () { | ... | ... |
src/styles/components/date-picker.less
| 1 | 1 | @date-picker-prefix-cls: ~"@{css-prefix}date-picker"; |
| 2 | +@picker-prefix-cls: ~"@{css-prefix}picker"; | |
| 2 | 3 | |
| 3 | 4 | .@{date-picker-prefix-cls} { |
| 4 | 5 | position: relative; |
| 5 | 6 | .@{select-dropdown-prefix-cls} { |
| 6 | 7 | width: auto; |
| 8 | + padding: 0; | |
| 7 | 9 | overflow: visible; |
| 8 | 10 | max-height: none; |
| 9 | 11 | } |
| ... | ... | @@ -100,4 +102,75 @@ |
| 100 | 102 | } |
| 101 | 103 | } |
| 102 | 104 | } |
| 105 | + | |
| 106 | + &-cells-year,&-cells-month{ | |
| 107 | + margin-top: 14px; | |
| 108 | + span{ | |
| 109 | + width: 40px; | |
| 110 | + height: 28px; | |
| 111 | + line-height: 28px; | |
| 112 | + margin: 10px 12px; | |
| 113 | + border-radius: @btn-border-radius-small; | |
| 114 | + em{ | |
| 115 | + width: 40px; | |
| 116 | + height: 28px; | |
| 117 | + line-height: 28px; | |
| 118 | + margin: 0; | |
| 119 | + } | |
| 120 | + } | |
| 121 | + } | |
| 122 | + | |
| 123 | + &-header{ | |
| 124 | + height: 32px; | |
| 125 | + line-height: 32px; | |
| 126 | + text-align: center; | |
| 127 | + border-bottom: 1px solid @border-color-split; | |
| 128 | + &-label{ | |
| 129 | + cursor: pointer; | |
| 130 | + transition: color @transition-time @ease-in-out; | |
| 131 | + &:hover{ | |
| 132 | + color: @primary-color; | |
| 133 | + } | |
| 134 | + } | |
| 135 | + } | |
| 136 | + &-prev-btn{ | |
| 137 | + float: left; | |
| 138 | + &-arrow-double{ | |
| 139 | + margin-left: 10px; | |
| 140 | + i:after{ | |
| 141 | + content: "\F3D2"; | |
| 142 | + } | |
| 143 | + } | |
| 144 | + } | |
| 145 | + &-next-btn{ | |
| 146 | + float: right; | |
| 147 | + &-arrow-double{ | |
| 148 | + margin-right: 10px; | |
| 149 | + i:after{ | |
| 150 | + content: "\F3D3"; | |
| 151 | + } | |
| 152 | + } | |
| 153 | + } | |
| 154 | +} | |
| 155 | + | |
| 156 | +.@{picker-prefix-cls} { | |
| 157 | + &-panel{ | |
| 158 | + &-icon-btn{ | |
| 159 | + display: inline-block; | |
| 160 | + width: 20px; | |
| 161 | + height: 24px; | |
| 162 | + line-height: 26px; | |
| 163 | + margin-top: 4px; | |
| 164 | + text-align: center; | |
| 165 | + cursor: pointer; | |
| 166 | + color: @btn-disable-color; | |
| 167 | + transition: color @transition-time @ease-in-out; | |
| 168 | + &:hover{ | |
| 169 | + color: @primary-color; | |
| 170 | + } | |
| 171 | + i{ | |
| 172 | + font-size: 14px; | |
| 173 | + } | |
| 174 | + } | |
| 175 | + } | |
| 103 | 176 | } |
| 104 | 177 | \ No newline at end of file | ... | ... |
test/routers/date.vue
| 1 | 1 | <template> |
| 2 | 2 | <div style="margin: 50px"> |
| 3 | - <date-picker style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options"></date-picker> | |
| 3 | + <br> | |
| 4 | + <row> | |
| 5 | + <i-col span="4"> | |
| 6 | + <date-picker style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options" @on-change="change" @on-open-change="change2" :format="format"></date-picker> | |
| 7 | + </i-col> | |
| 8 | + <i-col span="4"> | |
| 9 | + <date-picker type="year" style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options"></date-picker> | |
| 10 | + </i-col> | |
| 11 | + <i-col span="4"> | |
| 12 | + <date-picker type="month" style="width:200px" placeholder="请选择日期" :value.sync="value" :options="options"></date-picker> | |
| 13 | + </i-col> | |
| 14 | + </row> | |
| 4 | 15 | </div> |
| 5 | 16 | </template> |
| 6 | 17 | <script> |
| 7 | 18 | export default { |
| 8 | 19 | data () { |
| 9 | 20 | return { |
| 10 | - value: '2016-12-18', | |
| 21 | + value: new Date(), | |
| 11 | 22 | // value: '', |
| 12 | 23 | options: { |
| 13 | 24 | disabledDate(time) { |
| 14 | 25 | return time.getTime() < Date.now() - 8.64e7; |
| 15 | 26 | // return time && time.valueOf() < Date.now(); |
| 16 | 27 | } |
| 17 | - } | |
| 28 | + }, | |
| 29 | + format: 'yyyy/MM/dd' | |
| 18 | 30 | } |
| 19 | 31 | }, |
| 20 | 32 | computed: {}, |
| 21 | - methods: {} | |
| 33 | + methods: { | |
| 34 | + change (date) { | |
| 35 | +// console.log(date) | |
| 36 | + }, | |
| 37 | + change2 (s) { | |
| 38 | +// console.log(s) | |
| 39 | + } | |
| 40 | + } | |
| 22 | 41 | } |
| 23 | 42 | </script> |
| 24 | 43 | \ No newline at end of file | ... | ... |