From dd6f2eaa4987deb45e0f811f88a46fbee056a25c Mon Sep 17 00:00:00 2001 From: mengxiong10 <15623530290@163.com> Date: Sat, 12 Jan 2019 19:44:20 +0800 Subject: [PATCH] feat: add `valueType` to format binding value --- README.md | 71 +++++++++++++++++++++++++---------------- README.zh-CN.md | 72 +++++++++++++++++++++++++++--------------- src/index.vue | 49 ++++++++++++++++++---------- src/utils/index.js | 2 +- src/utils/transform.js | 40 +++++++++++++++++++++++ test/index.spec.js | 30 ++++++++++++++++++ test/language.spec.js | 11 +++++++ test/transform.spec.js | 29 +++++++++++++++++ 8 files changed, 233 insertions(+), 71 deletions(-) create mode 100644 src/utils/transform.js create mode 100644 test/language.spec.js create mode 100644 test/transform.spec.js diff --git a/README.md b/README.md index e0345bd..d790de8 100644 --- a/README.md +++ b/README.md @@ -69,34 +69,51 @@ export default { ``` ### Props -| Prop | Type | Default | Description | -|---------------------|---------------|-------------|-----------------------------------------------------| -| type | String | 'date' | select date type (date/datetime/year/month/time) | -| range | Boolean | false | if true, the type is daterange or datetimerange | -| format | String | YYYY-MM-DD | The parsing tokens are similar to the moment.js | -| lang | String/Object | zh | Translation ([custom](#lang))(en/zh/es/pt-br/fr/ru/de/it/cs) | -| clearable | Boolean | true | if false, don't show the clear icon | -| confirm | Boolean | false | if true, need click the button to change the value | -| editable | Boolean | true | if false, user cann't type it | -| disabled | Boolean | false | Disable the component | -| placeholder | String | | input placeholder text | -| width | String/Number | 210 | input size | -| append-to-body | Boolean | false | append the popup to body | -| popupStyle | Object | | popup style(override the top, left style) | -| not-before | String/Date | '' | Disable all dates before new Date(not-before) | -| not-after | String/Date | '' | Disable all dates after new Date(not-after) | -| disabled-days | Array/function| [] | Disable Days | -| shortcuts | Boolean/Array | true | the shortcuts for the range picker | -| time-picker-options | Object | {} | set timePickerOptions(start, step, end) | -| minute-step | Number | 0 | if > 0 don't show the second picker(0 - 60) | -| first-day-of-week | Number | 7 | set the first day of week (1-7) | -| input-class | String | 'mx-input' | the input class name | -| input-name | String | 'date' | the input name attr | -| input-attr | Object | | the input attr(eg: { required: true, id: 'input'}) | -| confirm-text | String | 'OK' | the default text to display on confirm button | -| range-separator | String | '~' | the range separator text | -| date-format | String | '' | format the time header and tooltip | +| Prop | Type | Accepted Values | Default | Description | +|---------------------|---------------|-----------------|-------------|-----------------------------------------------------| +| type | String | date/datetime/year/month/time | 'date' | select date type | +| range | Boolean | — | false | if true, the type is daterange or datetimerange | +| format | String | — | YYYY-MM-DD | The parsing tokens are similar to the moment.js | +| value-type | String/Object | date/format/timestamp | 'date' | type of binding value. If not specified, the binding value will be a Date object(see [detail](#value-type)) | +| lang | String/Object | en/zh/es/pt-br/fr/ru/de/it/cs | zh | Translation (set [how to custom](#lang)) | +| clearable | Boolean | — | true | if false, don't show the clear icon | +| confirm | Boolean | — | false | if true, need click the button to change the value | +| editable | Boolean | — | true | if false, user cann't type it | +| disabled | Boolean | — | false | Disable the component | +| placeholder | String | — | | input placeholder text | +| width | String/Number | — | 210 | input size | +| append-to-body | Boolean | — | false | append the popup to body | +| popupStyle | Object | — | | popup style(override the top, left style) | +| not-before | String/Date | — | '' | Disable all dates before new Date(not-before) | +| not-after | String/Date | — | '' | Disable all dates after new Date(not-after) | +| disabled-days | Array/function| — | [] | Disable Days | +| shortcuts | Boolean/Array | — | true | the shortcuts for the range picker | +| time-picker-options | Object | — | {} | set timePickerOptions(start, step, end) | +| minute-step | Number | 0 - 60 | 0 | if > 0 don't show the second picker | +| first-day-of-week | Number | 1 - 7 | 7 | set the first day of week | +| input-class | String | — | 'mx-input' | the input class name | +| input-attr | Object | — | | the input attr(eg: { required: true, id: 'input'}) | +| confirm-text | String | — | 'OK' | the default text to display on confirm button | +| range-separator | String | — | '~' | the range separator text | +| date-format | String | — | '' | format the time header and tooltip | +#### value-type +set the format of binding value + +| Value | Description | +|-----------------|-------------------------------------------| +| date | binding value will be a Date object | +| timestamp | binding value will be a timestamp number | +| format | binding value will be the format string | + +Advanced: You can also customize objects to implement two functions. +```js +{ + value2date: (value: any) => Date, // transform the binding value to calendar Date Object + date2value: (date: Date) => any // transform the calendar Date Object to binding value +} + +``` #### lang * String (en/zh/es/pt-br/fr/ru/de/it/cs) diff --git a/README.zh-CN.md b/README.zh-CN.md index 73b07af..ce17c23 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -69,32 +69,52 @@ export default { ``` ### Props -| Prop | Type | Default | Description -|---------------------|---------------|-------------|----------------------------------------------------- -| type | String | 'date' | 选择日期或日期时间(可选:date,datetime,year,month,time) -| range | Boolean | false | 如果是true, 显示日历范围选择 -| format | String | YYYY-MM-DD | 格式化显示日期 api类似moment.js -| lang | String/Object | zh | 选择语言或自定义 (en/zh/es/pt-br/fr/ru/de/it/cs)(custom) -| clearable | Boolean | true | 如果设置false, 不显示清除图标 -| confirm | Boolean | false | 如果是true, 显示确认按钮且需要确认才更新时间 -| editable | Boolean | true | 如果是false, 用户不能手动输入更新日期 -| disabled | Boolean | false | 禁用组件 -| placeholder | String | | 输入框placeholder -| width | String/Number | 210 | 设置宽度 -| append-to-body | Boolean | false | 弹出层放到body下面 -| popup-style | Object | | 弹出层的样式(可以覆盖left,top样式) -| not-before | String/Date | '' | 禁止选择这个时间之前的时间 -| not-after | String/Date | '' | 禁止选择这个时间之前=后的时间 -| disabled-days | Array/function| [] | 自定义禁止的日期 -| shortcuts | Boolean/Array | true | 自定义范围选择的时候快捷选项(见下表) -| time-picker-options | Object | {} | 自定义时间选择的开始,结束,步进(见下表) -| minute-step | Number | 0 | 设置分钟的步进, 设置大于0不显示秒的选择(0-60) -| first-day-of-week | Number | 7 | 设置日历星期几开头(1-7) -| input-class | String | 'mx-input' | 自定义输入框的类名 -| input-name | String | 'date' | 自定义input 的 name 属性 -| confirm-text | String | 'OK' | 确认按钮的名称 -| range-separator | String | '~' | range 分隔符 -| date-format | String | '' | 格式化时间组件头部和日历的tooltip,默认是format字段去除时间的格式化 +| 属性 | 类型 | 可选值 | 默认值 | 描述 +|---------------------|---------------| ---------------------- |-------------| ----------- +| type | String | date,datetime,year,month,time | 'date' | 选择日期或日期时间 +| range | Boolean | - | false | 如果是true, 显示日历范围选择 +| format | String | - | YYYY-MM-DD | 格式化显示日期 api类似moment.js +| value-type | String/Object | date/format/timestamp | 'date' | 设置绑定值的格式([详情](#value-type)) | +| lang | String/Object | en/zh/es/pt-br/fr/ru/de/it/cs| zh | 选择语言或自定义 ([自定义](#lang)) +| clearable | Boolean | - | true | 如果设置false, 不显示清除图标 +| confirm | Boolean | - | false | 如果是true, 显示确认按钮且需要确认才更新时间 +| editable | Boolean | - | true | 如果是false, 用户不能手动输入更新日期 +| disabled | Boolean | - | false | 禁用组件 +| placeholder | String | - | | 输入框placeholder +| width | String/Number | - | 210 | 设置宽度 +| append-to-body | Boolean | - | false | 弹出层放到body下面 +| popup-style | Object | - | | 弹出层的样式(可以覆盖left,top样式) +| not-before | String/Date | - | '' | 禁止选择这个时间之前的时间 +| not-after | String/Date | - | '' | 禁止选择这个时间之前=后的时间 +| disabled-days | Array/function| - | [] | 自定义禁止的日期 +| shortcuts | Boolean/Array | - | true | 自定义范围选择的时候快捷选项(见下表) +| time-picker-options | Object | - | {} | 自定义时间选择的开始,结束,步进(见下表) +| minute-step | Number | 0 - 60 | 0 | 设置分钟的步进, 设置大于0不显示秒的选择(0-60) +| first-day-of-week | Number | 1 - 7 | 7 | 设置日历星期几开头 +| input-class | String | - | 'mx-input' | 自定义input元素的类名 +| input-attr | Object | — | | 自定义input元是的属性(eg: { required: true, id: 'input', name:'date'}) +| confirm-text | String | - | 'OK' | 确认按钮的名称 +| range-separator | String | - | '~' | range 分隔符 +| date-format | String | - | '' | 格式化时间组件头部和日历的tooltip,默认是format字段去除时间的格式化 + + +#### value-type +设置绑定值的格式 + +| 可选值 | 描述 +|-----------------|--------------------------------------- +| date | 返回的绑定值是Date对象 +| timestamp | 返回的绑定值是时间戳数字 +| format | 返回的绑定值是通过`format`属性格式化的值 + +高级: 也可以传入一个自定义实现包含2个函数的对象 +```js +{ + value2date: (value: any) => Date, // 转化绑定值到日历时间对象 + date2value: (date: Date) => any // 转化日历时间对象到绑定值 +} + +``` #### lang * String (en/zh/es/pt-br/fr/ru/de/it/cs) diff --git a/src/index.vue b/src/index.vue index de0b95f..99721fa 100644 --- a/src/index.vue +++ b/src/index.vue @@ -109,6 +109,7 @@ import fecha from 'fecha' import clickoutside from '@/directives/clickoutside' import { isValidDate, isValidRange, isDateObejct, isPlainObject, formatDate, parseDate, throttle } from '@/utils/index' +import { transformDate, transformDateRange } from '@/utils/transform' import CalendarPanel from './calendar.vue' import locale from '@/mixins/locale' import Languages from '@/locale/languages' @@ -123,6 +124,12 @@ export default { }, props: { value: null, + valueType: { + default: 'date', + validator: function (value) { + return ['timestamp', 'format', 'date'].indexOf(value) !== -1 || isPlainObject(value) + } + }, placeholder: { type: String, default: null @@ -217,6 +224,14 @@ export default { } }, computed: { + transform () { + const obj = this.range ? transformDateRange : transformDate + const type = this.valueType + if (isPlainObject(type)) { + return { ...obj.date, ...type } + } + return obj[type] || obj.date + }, language () { if (isPlainObject(this.lang)) { return { ...Languages.en, ...this.lang } @@ -233,11 +248,12 @@ export default { if (this.userInput !== null) { return this.userInput } + const date = this.transform.value2date(this.value, this.format) if (!this.range) { - return isValidDate(this.value) ? this.stringify(this.value) : '' + return date ? this.stringify(date) : '' } - return isValidRange(this.value) - ? `${this.stringify(this.value[0])} ${this.rangeSeparator} ${this.stringify(this.value[1])}` + return Array.isArray(date) && date[0] && date[1] + ? `${this.stringify(date[0])} ${this.rangeSeparator} ${this.stringify(date[1])}` : '' }, computedWidth () { @@ -264,28 +280,28 @@ export default { { text: pickers[0], onClick (self) { - self.currentValue = [ new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 7) ] + self.currentValue = [new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 7)] self.updateDate(true) } }, { text: pickers[1], onClick (self) { - self.currentValue = [ new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 30) ] + self.currentValue = [new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 30)] self.updateDate(true) } }, { text: pickers[2], onClick (self) { - self.currentValue = [ new Date(Date.now() - 3600 * 1000 * 24 * 7), new Date() ] + self.currentValue = [new Date(Date.now() - 3600 * 1000 * 24 * 7), new Date()] self.updateDate(true) } }, { text: pickers[3], onClick (self) { - self.currentValue = [ new Date(Date.now() - 3600 * 1000 * 24 * 30), new Date() ] + self.currentValue = [new Date(Date.now() - 3600 * 1000 * 24 * 30), new Date()] self.updateDate(true) } } @@ -346,7 +362,7 @@ export default { if (typeof range.onClick === 'function') { return range.onClick(this) } - this.currentValue = [ new Date(range.start), new Date(range.end) ] + this.currentValue = [new Date(range.start), new Date(range.end)] this.updateDate(true) }, clearDate () { @@ -360,7 +376,7 @@ export default { if (valid) { this.updateDate(true) } - this.$emit('confirm', this.currentValue) + this.emitDate('confirm') this.closePopup() }, updateDate (confirm = false) { @@ -371,16 +387,15 @@ export default { if (equal) { return false } - this.$emit('input', this.currentValue) - this.$emit('change', this.currentValue) + this.emitDate('input') + this.emitDate('change') return true }, + emitDate (eventName) { + this.$emit(eventName, this.transform.date2value(this.currentValue, this.format)) + }, handleValueChange (value) { - if (!this.range) { - this.currentValue = isValidDate(value) ? new Date(value) : null - } else { - this.currentValue = isValidRange(value) ? [new Date(value[0]), new Date(value[1])] : [null, null] - } + this.currentValue = this.transform.value2date(value, this.format) }, selectDate (date) { this.currentValue = date @@ -484,7 +499,7 @@ export default { const start = this.parseDate(range[0], this.format) const end = this.parseDate(range[1], this.format) if (start && end && !checkDate(start, null, end) && !checkDate(end, start, null)) { - this.currentValue = [ start, end ] + this.currentValue = [start, end] this.updateDate(true) this.closePopup() return diff --git a/src/utils/index.js b/src/utils/index.js index c905017..0b06b68 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -66,7 +66,7 @@ export function parseDate (value, format) { try { return fecha.parse(value, format) } catch (e) { - return false + return null } } diff --git a/src/utils/transform.js b/src/utils/transform.js new file mode 100644 index 0000000..ce8489e --- /dev/null +++ b/src/utils/transform.js @@ -0,0 +1,40 @@ +import { isValidDate, isValidRange, parseDate, formatDate } from './index' + +export const transformDate = { + date: { + value2date: (value) => isValidDate(value) ? new Date(value) : null, + date2value: (date) => date + }, + timestamp: { + value2date: (value) => isValidDate(value) ? new Date(value) : null, + date2value: (date) => isValidDate(date) ? new Date(date).getTime() : null + }, + format: { + value2date: parseDate, + date2value: (date, format) => isValidDate(date) ? formatDate(date, format) : null + } +} + +export const transformDateRange = { + date: { + value2date: (value) => isValidRange(value) ? [new Date(value[0]), new Date(value[1])] : [null, null], + date2value: (date) => date + }, + timestamp: { + value2date: (value) => isValidRange(value) ? [new Date(value[0]), new Date(value[1])] : [null, null], + date2value: (date) => date.map(transformDate.timestamp.date2value) + }, + format: { + value2date: (value, format) => { + if (Array.isArray(value) && value.length === 2) { + const value0 = parseDate(value[0], format) + const value1 = parseDate(value[1], format) + if (value0 && value1 && value1 >= value0) { + return [value0, value1] + } + } + return [null, null] + }, + date2value: (date, format) => date.map(v => transformDate.format.date2value(v, format)) + } +} diff --git a/test/index.spec.js b/test/index.spec.js index 8341849..dda9b30 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -5,6 +5,7 @@ import CalendarPanel from '../src/calendar.vue' import DatePanel from '../src/panel/date' import TimePanel from '../src/panel/time' import YearPanel from '../src/panel/year' +import { transformDate, transformDateRange } from '../src/utils/transform' let wrapper @@ -13,6 +14,35 @@ afterEach(() => { }) describe('datepicker', () => { + it('prop: valueType', () => { + wrapper = mount(DatePicker, { + propsData: { + value: new Date(2018, 4, 2) + } + }) + const vm = wrapper.vm + expect(vm.transform).toBe(transformDate.date) + wrapper.setProps({ valueType: 'timestamp' }) + expect(vm.transform).toBe(transformDate.timestamp) + wrapper.setProps({ valueType: 'format' }) + expect(vm.transform).toBe(transformDate.format) + + wrapper.setProps({ valueType: 'date', range: true }) + expect(vm.transform).toBe(transformDateRange.date) + wrapper.setProps({ valueType: 'timestamp' }) + expect(vm.transform).toBe(transformDateRange.timestamp) + wrapper.setProps({ valueType: 'format' }) + expect(vm.transform).toBe(transformDateRange.format) + + const fn = (date) => date + wrapper.setProps({ valueType: { + date2value: fn, + value2date: fn + }}) + expect(vm.transform).toHaveProperty('date2value', fn) + expect(vm.transform).toHaveProperty('value2date', fn) + }) + it('prop: inputAttr', () => { wrapper = mount(DatePicker, { propsData: { diff --git a/test/language.spec.js b/test/language.spec.js new file mode 100644 index 0000000..539cd63 --- /dev/null +++ b/test/language.spec.js @@ -0,0 +1,11 @@ +import lang from '../src/locale/languages' + +const testLang = (key) => it(key, () => { + expect(lang[key].days).toHaveLength(7) + expect(lang[key].months).toHaveLength(12) + expect(lang[key].pickers).toHaveLength(4) +}) + +describe('transformDate', () => { + Object.keys(lang).forEach(key => testLang(key)) +}) diff --git a/test/transform.spec.js b/test/transform.spec.js new file mode 100644 index 0000000..e575d60 --- /dev/null +++ b/test/transform.spec.js @@ -0,0 +1,29 @@ +import { transformDate, transformDateRange } from '../src/utils/transform' + +const time = new Date(2019, 1, 3) +const timestamp = time.getTime() +const format = 'MM-DD-YYYY' +const text = '02-03-2019' + +const testfn = ({ type, value, date, err = null, range = false }) => it(`${type}}`, () => { + const obj = range ? transformDateRange : transformDate + const typeObj = obj[type] + expect(typeObj.value2date(err, format)).toEqual(err) + expect(typeObj.value2date(value, format)).toEqual(date) + expect(typeObj.date2value(err, format)).toEqual(err) + expect(typeObj.date2value(date, format)).toEqual(value) +}) + +describe('transformDate', () => { + testfn({ type: 'date', value: time, date: time }) + testfn({ type: 'format', value: text, date: time }) + testfn({ type: 'timestamp', value: timestamp, date: time }) +}) + +describe('transformDateRange', () => { + const err = [null, null] + const date = [time, time] + testfn({ type: 'date', value: [time, time], date, err, range: true }) + testfn({ type: 'format', value: [text, text], date, err, range: true }) + testfn({ type: 'timestamp', value: [timestamp, timestamp], date, err, range: true }) +})