From cbff0f31e9a5f77011e746e81341a8f742c62828 Mon Sep 17 00:00:00 2001 From: "jlj05024111@163.com" Date: Mon, 2 Dec 2024 20:11:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=97=A5=E5=8E=86=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E9=80=89=E6=8B=A9=E5=85=AC=E5=85=B1=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 + .../components/range-picker/range-picker.scss | 121 +++++ .../components/range-picker/range-picker.tsx | 313 +++++++++++++ .../single-picker/single-picker.scss | 66 +++ .../single-picker/single-picker.tsx | 151 ++++++ .../date-range-picker/date-range-picker.scss | 164 ++----- .../date-range-picker/date-range-picker.tsx | 443 ++++++------------ src/locale/en/index.ts | 2 + src/locale/zh-CN/index.ts | 2 + 9 files changed, 844 insertions(+), 422 deletions(-) create mode 100644 src/common/date-range-picker/components/range-picker/range-picker.scss create mode 100644 src/common/date-range-picker/components/range-picker/range-picker.tsx create mode 100644 src/common/date-range-picker/components/single-picker/single-picker.scss create mode 100644 src/common/date-range-picker/components/single-picker/single-picker.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f669490..11dac7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Change + +- 日历模式日期选择公共组件新增支持单个日期选择 + ## [0.0.37] - 2024-11-30 ### Added diff --git a/src/common/date-range-picker/components/range-picker/range-picker.scss b/src/common/date-range-picker/components/range-picker/range-picker.scss new file mode 100644 index 00000000..36ebfa63 --- /dev/null +++ b/src/common/date-range-picker/components/range-picker/range-picker.scss @@ -0,0 +1,121 @@ +@include b('range-picker-calendar-input') { + display: flex; + gap: getCssVar(spacing, base); + justify-content: center; + width: 100vw; + + @include e('start') { + position: relative; + display: flex; + flex-direction: column; + align-items: start; + justify-content: left; + max-width: 40%; + padding: getCssVar(spacing, base); + margin-top: getCssVar(spacing, base); + text-align: left; + background-color: getCssVar(color, fill, 3); + + @include m('label') { + position: absolute; + top: 50%; + z-index: 1; + font-size: getCssVar(font-size, header, 4); + color: getCssVar(color, tertiary); + transition: all linear 0.1s; + transform: translateY(-50%); + } + @include m('input') { + margin-top: getCssVar(spacing, base); + background-color: getCssVar(color, fill, 3); + opacity: 0; + } + @include when('show-input') { + .#{bem('range-picker-calendar-input','start')} { + @include m('label') { + position: absolute; + top: 0%; + margin-top: getCssVar(spacing, base); + font-size: getCssVar(font-size, small); + color: getCssVar(color, primary); + transform: unset; + } + @include m('input') { + opacity: 1; + } + } + } + @include when('focus') { + border-bottom: 1px solid getCssVar(color, primary); + } + @include when('error') { + border-bottom: 1px solid getCssVar(color, danger); + .#{bem('range-picker-calendar-input','start')} { + @include m('label') { + color: getCssVar(color, danger); + } + } + } + } + @include e('end') { + position: relative; + display: flex; + flex-direction: column; + align-items: start; + justify-content: left; + max-width: 40%; + padding: getCssVar(spacing, base); + margin-top: getCssVar(spacing, base); + text-align: left; + background-color: getCssVar(color, fill, 3); + + @include m('label') { + position: absolute; + top: 50%; + z-index: 1; + font-size: getCssVar(font-size, header, 4); + color: getCssVar(color, tertiary); + transition: all linear 0.1s; + transform: translateY(-50%); + } + @include m('input') { + margin-top: getCssVar(spacing, base); + background-color: getCssVar(color, fill, 3); + opacity: 0; + } + @include when('show-input') { + .#{bem('range-picker-calendar-input','end')} { + @include m('label') { + position: absolute; + top: 0%; + margin-top: getCssVar(spacing, base); + font-size: getCssVar(font-size, small); + color: getCssVar(color, primary); + transform: unset; + } + @include m('input') { + opacity: 1; + } + } + } + @include when('focus') { + border-bottom: 1px solid getCssVar(color, primary); + } + @include when('error') { + border-bottom: 1px solid getCssVar(color, danger); + .#{bem('range-picker-calendar-input','end')} { + @include m('label') { + color: getCssVar(color, danger); + } + } + } + } + + @include e('error') { + position: absolute; + top: calc(100% + getCssVar(spacing,tight)); + font-size: getCssVar(font-size, small); + color: getCssVar(color, danger); + white-space: pre-line; + } + } \ No newline at end of file diff --git a/src/common/date-range-picker/components/range-picker/range-picker.tsx b/src/common/date-range-picker/components/range-picker/range-picker.tsx new file mode 100644 index 00000000..24dc6bfb --- /dev/null +++ b/src/common/date-range-picker/components/range-picker/range-picker.tsx @@ -0,0 +1,313 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { debounce } from '@ibiz-template/core'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { defineComponent, Ref, ref, watch } from 'vue'; +import dayjs from 'dayjs'; +import { + validateDate, + validateRange, + validateResults, +} from '../../date-range-picker.util'; +import './range-picker.scss'; + +export const RangePicker = defineComponent({ + name: 'RangePicker', + props: { + value: { + type: Array, + default: null, + }, + // 格式化串,会将格式化之后的值返回回去 + format: { + type: String, + default: 'YYYY-MM-DD', + }, + // 时间选择范围,默认前后10年 + range: { + type: Number, + default: 10, + }, + }, + emits: ['change'], + setup(props, { emit }) { + const ns = useNamespace('range-picker'); + + // 选中值 + const select: Ref = ref([]); + + // 年月日国际化 + const tYear = ibiz.i18n.t('component.dateRangePicker.year'); + const tMonth = ibiz.i18n.t('component.dateRangePicker.month'); + const tDay = ibiz.i18n.t('component.dateRangePicker.day'); + + // 编辑态开始时间input框绑定变量 + const startDate = ref({ + editorRef: null, // 开始时间的input框Ref + date: '', // input框输入的值 + showErrorInfo: false, // 显示错误信息 + isFocus: false, // 输入框是否聚焦 + errorMsg: '', // 错误信息 + isRangeError: false, // 是否超出范围限制 + }); + + // 编辑态结束时间input框绑定变量 + const endDate = ref({ + editorRef: null, // 结束时间的input框Ref + date: '', // input框输入的值 + showErrorInfo: false, // 显示错误信息 + isFocus: false, // 输入框是否聚焦 + errorMsg: '', // 错误信息 + isRangeError: false, // 是否超出范围限制 + }); + + // 监听值变化 + watch( + () => props.value, + () => { + if ( + !props.value || + !Array.isArray(props.value) || + props.value.length === 0 || + props.value[0] === '' || + props.value[1] === '' + ) { + select.value = []; + startDate.value.date = ''; + endDate.value.date = ''; + } else { + select.value = props.value; + // 抛出去的值可以根据format进行格式化,但手动输入的时候只能走YYYY/M/D 格式, IOS里 new Date()不识别以 '-'连接的方式 + startDate.value.date = dayjs(select.value[0]).format('YYYY/M/D'); + endDate.value.date = dayjs(select.value[1]).format('YYYY/M/D'); + } + }, + { + immediate: true, + deep: true, + }, + ); + + /** + * 绘制错误提示信息 + * + */ + const renderErrorInfo = (tag: 'START' | 'END') => { + return ( +
+ {tag === 'START' ? startDate.value.errorMsg : endDate.value.errorMsg} +
+ ); + }; + + /** + * 绘制开始时间和结束时间 范围异常时的提示 + * 返回false,表示范围异常,否则表示无异常或不满足范围异常检测条件 + * + */ + const calcRangeError = () => { + if (startDate.value.date && endDate.value.date) { + const start = validateDate(String(startDate.value.date)); + const end = validateDate(String(endDate.value.date)); + if (start && end) { + const startdate = new Date(startDate.value.date).getTime(); + const enddate = new Date(endDate.value.date).getTime(); + if (startdate > enddate) { + return false; + } + } + } + return true; + }; + + /** + * 输入框防抖 + * 对输入值进行校验,不合规的显示警告,合规的保存 + * + */ + const debounceInstance = debounce((...args: unknown[]) => { + // tag:START | END; START表示是开始时间 ,END 表示是结束时间 + // value:输入值 + const [tag, value] = args; + const result = validateDate(String(value)); + const date = tag === 'START' ? startDate.value : endDate.value; + if (value) { + if (!result) { + // 输入不合法 + date.showErrorInfo = true; + date.errorMsg = `${ibiz.i18n.t( + 'component.dateRangePicker.formatIsInvalid', + )}。\n ${ibiz.i18n.t( + 'component.dateRangePicker.use', + )} yyyy/m/d \n ${ibiz.i18n.t( + 'component.dateRangePicker.example', + )}: ${dayjs(new Date()).format('YYYY/MM/DD')}`; + } else if (!validateRange(String(value), props.range)) { + // 判断该输入是否超过规定年限 + // 超出年限 + date.errorMsg = `${ibiz.i18n.t( + 'component.dateRangePicker.overLimit', + )}: ${dayjs(String(value)).format( + `YYYY${tYear}MM${tMonth}DD${tDay}`, + )}`; + date.showErrorInfo = true; + } else { + date.showErrorInfo = false; + // 判断是否范围异常,即开始时间大于结束时间 + const rangeResult = calcRangeError(); + if (!rangeResult) { + startDate.value.isRangeError = true; + startDate.value.errorMsg = ibiz.i18n.t( + 'component.dateRangePicker.scopeIsInvalid', + ); + endDate.value.isRangeError = true; + } else { + // 输入合法,校验开始和结束两个值是否都正常,都正常的话保存起来 + const validateResult = validateResults( + [startDate.value.date, endDate.value.date], + props.range, + ); + if (validateResult) { + // 校验通过,保存数据 + select.value = [startDate.value.date, endDate.value.date].map( + (_item: string) => { + return dayjs(_item).format(props.format); + }, + ); + emit('change', select.value); + } + } + } + } else { + date.showErrorInfo = false; + } + }, 500); + + /** + * 输入框输入 + * + * @param {string} tag + * @param {string} value + */ + const onInput = (tag: 'START' | 'END', value: string) => { + if (tag === 'START') { + startDate.value.showErrorInfo = false; + } else { + endDate.value.showErrorInfo = false; + } + startDate.value.isRangeError = false; + endDate.value.isRangeError = false; + debounceInstance(tag, value); + }; + + // 聚焦 + const onfocus = (tag: 'START' | 'END') => { + if (tag === 'START') { + startDate.value.isFocus = true; + } else { + endDate.value.isFocus = true; + } + }; + + // 失焦 + const onBlur = (tag: 'START' | 'END') => { + if (tag === 'START') { + startDate.value.isFocus = false; + } else { + endDate.value.isFocus = false; + } + }; + + // 点击输入框容器-显示输入框 + const onClick = (tag: 'START' | 'END') => { + if (tag === 'START') { + if (startDate.value.editorRef) { + (startDate.value.editorRef as any).focus(); + } + } else if (endDate.value.editorRef) { + (endDate.value.editorRef as any).focus(); + } + }; + + return { + ns, + startDate, + endDate, + onInput, + onfocus, + onBlur, + onClick, + renderErrorInfo, + }; + }, + render() { + return ( +
+
+
this.onClick('START')} + > +
+ {ibiz.i18n.t('component.dateRangePicker.startDate')} +
+ { + this.startDate.editorRef = el; + }} + class={this.ns.bem('calendar-input', 'start', 'input')} + v-model={this.startDate.date} + placeholder='yyyy/m/d' + onInput={() => this.onInput('START', this.startDate.date)} + onFocus={() => this.onfocus('START')} + onBlur={() => this.onBlur('START')} + > + {(this.startDate.showErrorInfo || this.startDate.isRangeError) && + this.renderErrorInfo('START')} +
+
this.onClick('END')} + > +
+ {ibiz.i18n.t('component.dateRangePicker.endDate')} +
+ { + this.endDate.editorRef = el; + }} + class={this.ns.bem('calendar-input', 'end', 'input')} + v-model={this.endDate.date} + placeholder='yyyy/m/d' + onInput={() => this.onInput('END', this.endDate.date)} + onFocus={() => this.onfocus('END')} + onBlur={() => this.onBlur('END')} + > + {this.endDate.showErrorInfo && this.renderErrorInfo('END')} +
+
+
+ ); + }, +}); diff --git a/src/common/date-range-picker/components/single-picker/single-picker.scss b/src/common/date-range-picker/components/single-picker/single-picker.scss new file mode 100644 index 00000000..56046141 --- /dev/null +++ b/src/common/date-range-picker/components/single-picker/single-picker.scss @@ -0,0 +1,66 @@ +@include b('single-picker') { + width: 100vw; + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + padding: getCssVar(spacing, base); + margin-top: getCssVar(spacing, base); + @include e('container') { + position: relative; + display: flex; + flex-direction: column; + align-items: start; + justify-content: left; + width: 100%; + padding: getCssVar(spacing, base); + margin-top: getCssVar(spacing, base); + text-align: left; + background-color: getCssVar(color, fill, 3); + @include when('show-input') { + border-bottom: 1px solid getCssVar(color, primary); + @include e('input') { + opacity: 1; + } + @include e('label') { + opacity: 1; + left: getCssVar(spacing, base); + top: 0%; + margin-top: getCssVar(spacing, base); + font-size: getCssVar(font-size, small); + color: getCssVar(color, primary); + transform: unset; + } + } + @include when('error'){ + border-bottom: 1px solid getCssVar(color, danger); + @include e('label') { + color: getCssVar(color, danger); + } + } + } + + @include e('label') { + position: absolute; + top: 50%; + z-index: 1; + left: 50%; + font-size: getCssVar(font-size, header, 4); + color: getCssVar(color, tertiary); + transition: all linear 0.1s; + text-align: center; + transform: translate(-50%, -50%); + } + @include e('input') { + margin-top: getCssVar(spacing, base); + background-color: getCssVar(color, fill, 3); + opacity: 0; + } + @include e('error') { + position: absolute; + top: calc(100% + getCssVar(spacing, tight)); + font-size: getCssVar(font-size, small); + color: getCssVar(color, danger); + white-space: pre-line; + } +} diff --git a/src/common/date-range-picker/components/single-picker/single-picker.tsx b/src/common/date-range-picker/components/single-picker/single-picker.tsx new file mode 100644 index 00000000..4640aa09 --- /dev/null +++ b/src/common/date-range-picker/components/single-picker/single-picker.tsx @@ -0,0 +1,151 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { debounce } from '@ibiz-template/core'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { defineComponent, ref, watch } from 'vue'; +import dayjs from 'dayjs'; +import { validateDate, validateRange } from '../../date-range-picker.util'; +import './single-picker.scss'; + +export const SinglePicker = defineComponent({ + name: 'SinglePicker', + props: { + value: { + type: Array, + default: null, + }, + // 格式化串,会将格式化之后的值返回回去 + format: { + type: String, + default: 'YYYY-MM-DD', + }, + // 时间选择范围,默认前后10年 + range: { + type: Number, + default: 10, + }, + }, + emits: ['change'], + setup(props, { emit }) { + const ns = useNamespace('single-picker'); + // 当前输入值 + const curDate = ref(''); + // input框编辑器 + const editorRef = ref(); + // 错误消息 + const errorMsg = ref(''); + // 是否聚焦 + const isFoucs = ref(false); + + // 年月日国际化 + const tYear = ibiz.i18n.t('component.dateRangePicker.year'); + const tMonth = ibiz.i18n.t('component.dateRangePicker.month'); + const tDay = ibiz.i18n.t('component.dateRangePicker.day'); + + const debounceInstance = debounce((...args: unknown[]) => { + // value:输入值 + const [value] = args; + const result = validateDate(String(value)); + if (value) { + if (!result) { + // 输入不合法 + errorMsg.value = `${ibiz.i18n.t( + 'component.dateRangePicker.formatIsInvalid', + )}。\n ${ibiz.i18n.t( + 'component.dateRangePicker.use', + )} yyyy/m/d \n ${ibiz.i18n.t( + 'component.dateRangePicker.example', + )}: ${dayjs(new Date()).format('YYYY/MM/DD')}`; + } else if (!validateRange(String(value), props.range)) { + // 判断该输入是否超过规定年限 + // 超出年限 + errorMsg.value = `${ibiz.i18n.t( + 'component.dateRangePicker.overLimit', + )}: ${dayjs(String(value)).format( + `YYYY${tYear}MM${tMonth}DD${tDay}`, + )}`; + } else { + errorMsg.value = ''; + emit('change', [dayjs(curDate.value).format(props.format)]); + } + } else { + errorMsg.value = ''; + } + }, 500); + + // 输入框输入 + const onInput = () => { + errorMsg.value = ''; + debounceInstance(curDate.value); + }; + + // 聚焦 + const onfocus = () => { + isFoucs.value = true; + }; + + // 失焦 + const onBlur = () => { + isFoucs.value = false; + }; + + const onClick = () => { + editorRef.value?.focus(); + isFoucs.value = true; + }; + + watch( + () => props.value, + () => { + if (props.value && props.value[0]) { + curDate.value = dayjs(props.value[0]).format('YYYY/M/D'); + } else { + curDate.value = ''; + } + }, + { + immediate: true, + deep: true, + }, + ); + + return { + ns, + curDate, + editorRef, + isFoucs, + errorMsg, + onInput, + onfocus, + onBlur, + onClick, + }; + }, + render() { + return ( +
+
+
+ {ibiz.i18n.t('component.dateRangePicker.startDate')} +
+ +
{this.errorMsg}
+
+
+ ); + }, +}); diff --git a/src/common/date-range-picker/date-range-picker.scss b/src/common/date-range-picker/date-range-picker.scss index ea585d16..7ea0ac84 100644 --- a/src/common/date-range-picker/date-range-picker.scss +++ b/src/common/date-range-picker/date-range-picker.scss @@ -5,7 +5,7 @@ height: 100%; @include e('header') { padding: 0 16px; - color: getCssVar(color, white); + color: getCssVar(color, default); background-color: getCssVar(color, primary); } @include e('actions') { @@ -49,15 +49,42 @@ white-space: nowrap; align-items: center; } - @include m('icons'){ + @include m('icons') { display: flex; - gap: getCssVar(spacing, base); } } @include e('content') { flex: 1; } + @include e('single-text') { + margin: getCssVar(spacing, base) 0 getCssVar(spacing, base); + text-align: left; + @include m('placeholder') { + font-size: getCssVar(font-size, small); + } + @include m('date') { + display: flex; + gap: getCssVar(spacing, tight); + align-items: center; + justify-content: space-between; + margin: getCssVar(spacing, base) 0; + font-size: getCssVar(font-size, header-4); + } + @include m('date-text') { + display: flex; + gap: getCssVar(spacing, tight); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + align-items: center; + } + @include m('icons') { + display: flex; + + gap: getCssVar(spacing, base); + } + } } @include b('calendar-date-range-picker-calendar') { @include e('today') { @@ -65,6 +92,11 @@ color: getCssVar(color, primary); border: 1px solid getCssVar(color, primary); border-radius: 50%; + width: getCssVar(spacing, extra-loose); + height: getCssVar(spacing, extra-loose); + display: flex; + justify-content: center; + align-items: center; @include m('bottom-info') { position: absolute; right: 0; @@ -74,126 +106,20 @@ line-height: var(--van-calendar-info-line-height); } } -} - -@include b('calendar-date-range-picker-calendar-input') { - display: flex; - gap: getCssVar(spacing, base); - justify-content: center; - width: 100vw; - - @include e('start') { - position: relative; - display: flex; - flex-direction: column; - align-items: start; - justify-content: left; - max-width: 40%; - padding: getCssVar(spacing, base); - margin-top: getCssVar(spacing, base); - text-align: left; - background-color: getCssVar(color, fill, 3); - - @include m('label') { - position: absolute; - top: 50%; - z-index: 1; - font-size: getCssVar(font-size, header, 4); - color: getCssVar(color, tertiary); - transition: all linear 0.1s; - transform: translateY(-50%); - } - @include m('input') { - margin-top: getCssVar(spacing, base); - background-color: getCssVar(color, fill, 3); - opacity: 0; - } - @include when('show-input') { - .#{bem('calendar-date-range-picker-calendar-input','start')} { - @include m('label') { - position: absolute; - top: 0%; - margin-top: getCssVar(spacing, base); - font-size: getCssVar(font-size, small); - color: getCssVar(color, primary); - transform: unset; - } - @include m('input') { - opacity: 1; - } - } - } - @include when('focus') { - border-bottom: 1px solid getCssVar(color, primary); - } - @include when('error') { - border-bottom: 1px solid getCssVar(color, danger); - .#{bem('calendar-date-range-picker-calendar-input','start')} { - @include m('label') { - color: getCssVar(color, danger); - } + .van-calendar__day--start,.van-calendar__day--end { + @include e('today') { + color: getCssVar(color, default); + .ibiz-calendar-date-range-picker-calendar__today--bottom-info { + display: none; } } } - @include e('end') { - position: relative; - display: flex; - flex-direction: column; - align-items: start; - justify-content: left; - max-width: 40%; - padding: getCssVar(spacing, base); - margin-top: getCssVar(spacing, base); - text-align: left; - background-color: getCssVar(color, fill, 3); - - @include m('label') { - position: absolute; - top: 50%; - z-index: 1; - font-size: getCssVar(font-size, header, 4); - color: getCssVar(color, tertiary); - transition: all linear 0.1s; - transform: translateY(-50%); + .van-calendar__day--selected { + .van-calendar__selected-day{ + height: 100%; } - @include m('input') { - margin-top: getCssVar(spacing, base); - background-color: getCssVar(color, fill, 3); - opacity: 0; + @include e('today') { + color: getCssVar(color, default); } - @include when('show-input') { - .#{bem('calendar-date-range-picker-calendar-input','end')} { - @include m('label') { - position: absolute; - top: 0%; - margin-top: getCssVar(spacing, base); - font-size: getCssVar(font-size, small); - color: getCssVar(color, primary); - transform: unset; - } - @include m('input') { - opacity: 1; - } - } - } - @include when('focus') { - border-bottom: 1px solid getCssVar(color, primary); - } - @include when('error') { - border-bottom: 1px solid getCssVar(color, danger); - .#{bem('calendar-date-range-picker-calendar-input','end')} { - @include m('label') { - color: getCssVar(color, danger); - } - } - } - } - - @include e('error') { - position: absolute; - top: calc(100% + getCssVar(spacing,tight)); - font-size: getCssVar(font-size, small); - color: getCssVar(color, danger); - white-space: pre-line; } } diff --git a/src/common/date-range-picker/date-range-picker.tsx b/src/common/date-range-picker/date-range-picker.tsx index 88042610..57c84ab4 100644 --- a/src/common/date-range-picker/date-range-picker.tsx +++ b/src/common/date-range-picker/date-range-picker.tsx @@ -5,6 +5,7 @@ import { defineComponent, nextTick, onMounted, + PropType, Ref, ref, watch, @@ -12,16 +13,21 @@ import { import { useNamespace } from '@ibiz-template/vue3-util'; import dayjs from 'dayjs'; import './date-range-picker.scss'; -import { debounce } from '@ibiz-template/core'; -import { - validateDate, - validateRange, - validateResults, -} from './date-range-picker.util'; +import { RangePicker } from './components/range-picker/range-picker'; +import { SinglePicker } from './components/single-picker/single-picker'; export const IBizDateRangeCalendar = defineComponent({ name: 'IBizDateRangeCalendar', + components: { + RangePicker, + SinglePicker, + }, props: { + type: { + // range 表示是日期范围选择,single表示是单个日期选择,两种方式抛出值的时候如果无值,抛的就是undefined,有值的话抛的都是数组 + type: String as PropType<'range' | 'single'>, + default: 'range', + }, // 回显值 value: { type: Array, @@ -46,7 +52,7 @@ export const IBizDateRangeCalendar = defineComponent({ // 最小可选时间-当前时间往前10年 const minDate = new Date(`${new Date().getFullYear() - props.range}`); - // 选中值 + // 范围选择时选中值 const select: Ref = ref([]); // 允许保存 @@ -58,31 +64,6 @@ export const IBizDateRangeCalendar = defineComponent({ // 是否编辑态-可手动输入 const isEditable = ref(false); - // 编辑态开始时间input框绑定变量 - const startDate = ref({ - editorRef: null, // 开始时间的input框Ref - date: '', // input框输入的值 - showErrorInfo: false, // 显示错误信息 - isFocus: false, // 输入框是否聚焦 - errorMsg: '', // 错误信息 - isRangeError: false, // 是否超出范围限制 - }); - - // 编辑态结束时间input框绑定变量 - const endDate = ref({ - editorRef: null, // 结束时间的input框Ref - date: '', // input框输入的值 - showErrorInfo: false, // 显示错误信息 - isFocus: false, // 输入框是否聚焦 - errorMsg: '', // 错误信息 - isRangeError: false, // 是否超出范围限制 - }); - - // 年月日国际化 - const tYear = ibiz.i18n.t('component.dateRangePicker.year'); - const tMonth = ibiz.i18n.t('component.dateRangePicker.month'); - const tDay = ibiz.i18n.t('component.dateRangePicker.day'); - // 取消,不返回数据 const onCancel = () => { emit('cancel'); @@ -103,9 +84,6 @@ export const IBizDateRangeCalendar = defineComponent({ select.value = value.map((_item: Date) => { return dayjs(_item).format(props.format); }); - // 抛出去的值可以根据format进行格式化,但手动输入的时候只能走YYYY/M/D 格式, IOS里 new Date()不识别以 '-'连接的方式 - startDate.value.date = dayjs(select.value[0]).format('YYYY/M/D'); - endDate.value.date = dayjs(select.value[1]).format('YYYY/M/D'); } }; @@ -115,9 +93,14 @@ export const IBizDateRangeCalendar = defineComponent({ * @param {(Date | Date[])} value */ const onSelect = (_value: Date | Date[]) => { - allowSave.value = false; - // 清空select.value - select.value = []; + if (props.type === 'range') { + // 范围选择时单击清空select.value + allowSave.value = false; + select.value = []; + } else { + allowSave.value = true; + select.value = [dayjs(_value as Date).format(props.format)]; + } }; watch( @@ -126,18 +109,12 @@ export const IBizDateRangeCalendar = defineComponent({ if ( !props.value || !Array.isArray(props.value) || - props.value.length === 0 || - props.value[0] === '' || - props.value[1] === '' + props.value.length === 0 ) { allowSave.value = false; select.value = []; - startDate.value.date = ''; - endDate.value.date = ''; } else { select.value = props.value; - startDate.value.date = dayjs(select.value[0]).format('YYYY/M/D'); - endDate.value.date = dayjs(select.value[1]).format('YYYY/M/D'); allowSave.value = true; } }, @@ -153,18 +130,24 @@ export const IBizDateRangeCalendar = defineComponent({ * @return {*} */ const calcDefaultSelect = computed(() => { - if ( - !select.value || - !Array.isArray(select.value) || - select.value.length === 0 || - select.value[0] === '' || - select.value[1] === '' - ) { - return null; + if (props.type === 'range') { + if ( + !select.value || + !Array.isArray(select.value) || + select.value.length === 0 || + select.value[0] === '' || + select.value[1] === '' + ) { + return null; + } + return select.value.map((_item: string) => { + return new Date(dayjs(_item).format('YYYY/M/D')); + }); } - return select.value.map((_item: string) => { - return new Date(dayjs(_item).format('YYYY/M/D')); - }); + if (select.value && select.value[0]) { + return new Date(dayjs(select.value[0]).format('YYYY/M/D')); + } + return null; }); /** @@ -197,173 +180,35 @@ export const IBizDateRangeCalendar = defineComponent({ */ const switchEdit = () => { isEditable.value = !isEditable.value; - if (isEditable.value) { - if ( - select.value && - Array.isArray(select.value) && - select.value.length > 0 && - !!select.value[0] && - !!select.value[1] - ) { - startDate.value.date = dayjs(select.value[0]).format('YYYY/M/D'); - endDate.value.date = dayjs(select.value[1]).format('YYYY/M/D'); - } else { - startDate.value.date = ''; - endDate.value.date = ''; - } - } else if ( - !select.value || - !Array.isArray(select.value) || - select.value.length === 0 || - !select.value[0] || - !select.value[1] - ) { + if (!isEditable.value) { nextTick(() => { if (calendarRef.value) { - calendarRef.value.scrollToDate(new Date()); + if (!select.value[0]) { + calendarRef.value.scrollToDate(new Date()); + } else { + calendarRef.value.scrollToDate( + new Date(dayjs(select.value[0]).format('YYYY/M/D')), + ); + } } }); } }; - /** - * 绘制错误提示信息 - * - */ - const renderErrorInfo = (tag: 'START' | 'END') => { - return ( -
- {tag === 'START' ? startDate.value.errorMsg : endDate.value.errorMsg} -
- ); + // 值抛出 + const onChange = (value: string[]) => { + select.value = value; }; /** - * 绘制开始时间和结束时间 范围异常时的提示 - * 返回false,表示范围异常,否则表示无异常或不满足范围异常检测条件 + * 格式化单选日期 * */ - const calcRangeError = () => { - if (startDate.value.date && endDate.value.date) { - const start = validateDate(String(startDate.value.date)); - const end = validateDate(String(endDate.value.date)); - if (start && end) { - const startdate = new Date(startDate.value.date).getTime(); - const enddate = new Date(endDate.value.date).getTime(); - if (startdate > enddate) { - return false; - } - } - } - return true; - }; - - /** - * 输入框防抖 - * 对输入值进行校验,不合规的显示警告,合规的保存 - * - */ - const debounceInstance = debounce((...args: unknown[]) => { - // tag:START | END; START表示是开始时间 ,END 表示是结束时间 - // value:输入值 - const [tag, value] = args; - const result = validateDate(String(value)); - const date = tag === 'START' ? startDate.value : endDate.value; - if (value) { - if (!result) { - // 输入不合法 - date.showErrorInfo = true; - date.errorMsg = `${ibiz.i18n.t( - 'component.dateRangePicker.formatIsInvalid', - )}。\n ${ibiz.i18n.t( - 'component.dateRangePicker.use', - )} yyyy/m/d \n ${ibiz.i18n.t( - 'component.dateRangePicker.example', - )}: ${dayjs(new Date()).format('YYYY/MM/DD')}`; - } else if (!validateRange(String(value), props.range)) { - // 判断该输入是否超过规定年限 - // 超出年限 - date.errorMsg = `${ibiz.i18n.t( - 'component.dateRangePicker.overLimit', - )}: ${dayjs(String(value)).format( - `YYYY${tYear}MM${tMonth}DD${tDay}`, - )}`; - date.showErrorInfo = true; - } else { - date.showErrorInfo = false; - // 判断是否范围异常,即开始时间大于结束时间 - const rangeResult = calcRangeError(); - if (!rangeResult) { - startDate.value.isRangeError = true; - startDate.value.errorMsg = ibiz.i18n.t( - 'component.dateRangePicker.scopeIsInvalid', - ); - endDate.value.isRangeError = true; - } else { - // 输入合法,校验开始和结束两个值是否都正常,都正常的话保存起来 - const validateResult = validateResults( - [startDate.value.date, endDate.value.date], - props.range, - ); - if (validateResult) { - // 校验通过,保存数据 - select.value = [startDate.value.date, endDate.value.date].map( - (_item: string) => { - return dayjs(_item).format(props.format); - }, - ); - } - } - } - } else { - date.showErrorInfo = false; - } - }, 500); - - /** - * 输入框输入 - * - * @param {string} tag - * @param {string} value - */ - const onInput = (tag: 'START' | 'END', value: string) => { - if (tag === 'START') { - startDate.value.showErrorInfo = false; - } else { - endDate.value.showErrorInfo = false; - } - startDate.value.isRangeError = false; - endDate.value.isRangeError = false; - debounceInstance(tag, value); - }; - - // 聚焦 - const onfocus = (tag: 'START' | 'END') => { - if (tag === 'START') { - startDate.value.isFocus = true; - } else { - endDate.value.isFocus = true; - } - }; - - // 失焦 - const onBlur = (tag: 'START' | 'END') => { - if (tag === 'START') { - startDate.value.isFocus = false; - } else { - endDate.value.isFocus = false; - } - }; - - // 点击输入框容器-显示输入框 - const onClick = (tag: 'START' | 'END') => { - if (tag === 'START') { - if (startDate.value.editorRef) { - (startDate.value.editorRef as any).focus(); - } - } else if (endDate.value.editorRef) { - (endDate.value.editorRef as any).focus(); + const formatSingleDate = () => { + if (!select.value[0]) { + return ibiz.i18n.t('component.dateRangePicker.noSelect'); } + return dayjs(select.value[0]).format(props.format); }; /** @@ -372,72 +217,23 @@ export const IBizDateRangeCalendar = defineComponent({ * @return {*} */ const renderCalendarInput = () => { + if (props.type === 'range') { + return ( + + ); + } return ( -
-
onClick('START')} - > -
- {ibiz.i18n.t('component.dateRangePicker.startDate')} -
- { - startDate.value.editorRef = el; - }} - class={ns.bem('calendar-input', 'start', 'input')} - v-model={startDate.value.date} - placeholder='yyyy/m/d' - onInput={() => onInput('START', startDate.value.date)} - onFocus={() => onfocus('START')} - onBlur={() => onBlur('START')} - > - {(startDate.value.showErrorInfo || startDate.value.isRangeError) && - renderErrorInfo('START')} -
-
onClick('END')} - > -
- {ibiz.i18n.t('component.dateRangePicker.endDate')} -
- { - endDate.value.editorRef = el; - }} - class={ns.bem('calendar-input', 'end', 'input')} - v-model={endDate.value.date} - placeholder='yyyy/m/d' - onInput={() => onInput('END', endDate.value.date)} - onFocus={() => onfocus('END')} - onBlur={() => onBlur('END')} - > - {endDate.value.showErrorInfo && renderErrorInfo('END')} -
-
+ ); }; @@ -488,9 +284,72 @@ export const IBizDateRangeCalendar = defineComponent({ // 清除 const onClear = () => { - select.value = ['', '']; - startDate.value.date = ''; - endDate.value.date = ''; + if (props.type === 'range') { + select.value = ['', '']; + } else { + select.value = []; + } + }; + + // 绘制单个显示 + const renderSingleText = () => { + return ( +
+ + {ibiz.i18n.t('component.dateRangePicker.selectDate')} + +
+
+ {formatSingleDate()} +
+
+ + + + + {!isEditable.value ? ( + + ) : ( + + )} + +
+
+
+ ); + }; + + // 绘制范围选择显示 + const renderSelectRange = () => { + if (props.type === 'range') { + return ( +
+ + {ibiz.i18n.t('component.dateRangePicker.headerPlaceholder')} + +
+
+ {formatStartDate()} + ~ + {formatEndDate()} +
+
+ + + + + {!isEditable.value ? ( + + ) : ( + + )} + +
+
+
+ ); + } + return renderSingleText(); }; return { @@ -511,6 +370,7 @@ export const IBizDateRangeCalendar = defineComponent({ switchEdit, onClear, renderCalendarInput, + renderSelectRange, }; }, render() { @@ -532,30 +392,7 @@ export const IBizDateRangeCalendar = defineComponent({ -
- - {ibiz.i18n.t('component.dateRangePicker.headerPlaceholder')} - -
-
- {this.formatStartDate()} - ~ - {this.formatEndDate()} -
-
- - - - - {!this.isEditable ? ( - - ) : ( - - )} - -
-
-
+ {this.renderSelectRange()} {!this.isEditable ? (