diff --git a/devui/date-picker/components/helper.ts b/devui/date-picker/components/helper.ts index a756d082c922766301f6912bc2d86f4843a7eb39..805d977b50cf556a030f9e51fa2e37b756de3963 100644 --- a/devui/date-picker/components/helper.ts +++ b/devui/date-picker/components/helper.ts @@ -1,4 +1,4 @@ -import { invokeCallback, subDateDay } from './utils' +import { invokeCallback, subDateDay, betweenDate } from './utils' import { TDateCell, TDatePanelDataProps, TDatePanelProps } from './types' export const getDateKey = (date: Date): string => { @@ -6,16 +6,13 @@ export const getDateKey = (date: Date): string => { } export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = 'cell'): string => { - if(day.current !== 0) { - return `${base} disabled` - } - const { dateMin, dateMax } = props - if(dateMin && subDateDay(day.date, dateMin) < 0) { - return `${base} disabled` - } - if(dateMax && subDateDay(dateMax, day.date) < 0) { + + if(!betweenDate(day.date, props.dateMin, props.dateMax)) { return `${base} disabled` + } else if(day.current !== 0) { + return `${base} not-current` } + const key = getDateKey(day.date) if (props.type === 'range') { if (props.dateStart) { @@ -40,9 +37,10 @@ export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = } export const trigEvent = (props: TDatePanelProps, day: TDateCell): void => { - if(day.current !== 0) { + if(!betweenDate(day.date, props.dateMin, props.dateMax)) { return } + if (props.type === 'range') { if (!props.dateStart) { invokeCallback(props.onSelectStart, day.date) diff --git a/devui/date-picker/components/panel/index.scss b/devui/date-picker/components/panel/index.scss index 57ae53ec08f1f14132b3877b3b4ce868480e154b..9fb2adaea7a4cb6fec4a8d7a3e2a749d65cda733 100644 --- a/devui/date-picker/components/panel/index.scss +++ b/devui/date-picker/components/panel/index.scss @@ -13,7 +13,6 @@ $panel-cell-active-hover-color: #ffffff; .devui-calendar-panel { width: $panel-width; - height: $panel-height; padding: $panel-padding; box-sizing: border-box; overflow: hidden; @@ -55,9 +54,14 @@ $panel-cell-active-hover-color: #ffffff; background-color: $devui-disabled-bg; } - &.disabled { + &.disabled, + &.not-current { color: $devui-disabled-text; } + + &.disabled { + cursor: not-allowed; + } } } @@ -75,4 +79,25 @@ $panel-cell-active-hover-color: #ffffff; cursor: pointer; list-style: none; } + + .today-container { + padding: 8px 8px; + display: flex; + flex-direction: row; + justify-content: flex-end; + + &.disabled { + .today-button { + border: 1px solid #cccccc; + cursor: not-allowed; + } + } + + .today-button { + border: 1px solid #0066cc; + border-radius: 3px; + padding: 2px 20px; + font-size: 12px; + } + } } diff --git a/devui/date-picker/components/panel/index.tsx b/devui/date-picker/components/panel/index.tsx index fdef8292063c881f5d933a38e360176e4f3790a1..71526888a66bbf866e3006990317a96a2020a9ed 100644 --- a/devui/date-picker/components/panel/index.tsx +++ b/devui/date-picker/components/panel/index.tsx @@ -1,10 +1,12 @@ import { TDatePanelProps } from '../types' -import { getMonthWeeklyDays, WEEK_DAYS } from '../utils' +import { getMonthWeeklyDays, WEEK_DAYS, betweenDate } from '../utils' import { handleDateEnter, cellClassName, trigEvent } from '../helper' import Toolbar from '../toolbar' +import TodayDefault from '../today-default' import './index.scss' const CalendarDatePanel = (props: TDatePanelProps) => { + const today = new Date() return (
{ row.map(day => { return ( trigEvent(props as TDatePanelProps, day)} - onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} + class={cellClassName(props, day)} + onClick={() => trigEvent(props, day)} + onMouseenter={() => handleDateEnter(props, day)} >{day.date.getDate()} ) }) }) } + {props.type !== 'range' ? ( + { + typeof props.onToday === 'function' && props.onToday(today, 0) + }} + /> + ) : null}
) } diff --git a/devui/date-picker/components/popup/index.scss b/devui/date-picker/components/popup/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..d9208c50646644ddf80b52eef45fa9df09284277 --- /dev/null +++ b/devui/date-picker/components/popup/index.scss @@ -0,0 +1,14 @@ +.devui-datepicker-popup { + position: fixed; + left: 0; + top: 0; + width: 0; + height: 0; + background-color: rgba(0, 0, 0, 0.2); + z-index: 99; + overflow: visible; + + .popup-tracing { + position: absolute; + } +} diff --git a/devui/date-picker/components/popup/index.tsx b/devui/date-picker/components/popup/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f241927b34a43764e07ffe34803e639e634c4857 --- /dev/null +++ b/devui/date-picker/components/popup/index.tsx @@ -0,0 +1,109 @@ +import { defineComponent, reactive, renderSlot, ref, useSlots, onMounted, onUnmounted, onBeforeUpdate } from 'vue' +import { EventManager, isIn, traceNode, invokeFunction } from '../../utils' +import { + handlePositionFactory, + formatValue, + formatPlaceholder, + getAttachInputDom, +} from '../../helper' + +import './index.scss' + +type TState = { + x?: string + y?: string + attachInputDom?: string + show: boolean + st: boolean +} + +export default defineComponent({ + name: 'DDatePickerPopup', + props: { + attach: { type: String }, + onBinding: { type: Function }, + onClosed: { type: Function }, + onOpen: { type: Function }, + show: { type: Boolean }, + }, + setup(props) { + + const container = ref() + const evtman = new EventManager() + const state = reactive({ + x: '0', + y: '0', + st: true, + show: !!props.show, + }) + + let el: Element | null = null + + // 弹出层跟踪 + const handlePosition = handlePositionFactory(state, props, container) + + onBeforeUpdate(() => { + state.show = !!props.show + }) + + onMounted(() => { + // 获取绑定节点(默认input) + el = getAttachInputDom(props) + // 绑定节点不存在,作为普通组件展示。 + if (!el) { + state.st = true + state.show = true + return + } else { + state.show = false + state.st = false + } + + invokeFunction(props.onBinding) + + // 绑定节点click事件处理弹出层显示 + evtman.append(el, 'click', () => { + if(!state.show) { + state.show = true + invokeFunction(props.onOpen) + } + }) + // document层处理`点击其他区域隐藏` + evtman.append(document, 'click', (e: MouseEvent) => { + if (!state.show || e.target === el || isIn(e.target as Node, container.value)) { + return + } + state.show = false + invokeFunction(props.onClosed) + // reset() + }) + // 对绑定节点做scroll跟踪,并绑定跟踪事件 + traceNode(el).forEach(node => { + evtman.append(node, 'scroll', handlePosition) + }) + }) + + onUnmounted(() => { + evtman.dispose() + }) + + return () => { + const defaultSlot = renderSlot(useSlots(), 'default') + if (state.st) { + return defaultSlot + } + handlePosition() + return ( +
+ +
+ ) + } + } +}) \ No newline at end of file diff --git a/devui/date-picker/components/today-default/index.tsx b/devui/date-picker/components/today-default/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9ed09cbf7bfe60ed4fd7f2fbf9ae7a53afdeefb4 --- /dev/null +++ b/devui/date-picker/components/today-default/index.tsx @@ -0,0 +1,19 @@ +type TProps = { + onSelected?: (date: Date) => void + disabled?: boolean +} + +const TodayDefault = (props: TProps) => { + const { onSelected = () => 0, disabled = false } = props + return ( +
+ +
+ ) +} + +export default TodayDefault \ No newline at end of file diff --git a/devui/date-picker/components/types.ts b/devui/date-picker/components/types.ts index 66db15209623c554c4ba609bdfb04080a869fd4e..d11595ee1943cd19a29f241a552e009653e6a3e5 100644 --- a/devui/date-picker/components/types.ts +++ b/devui/date-picker/components/types.ts @@ -38,12 +38,13 @@ export type TDatePanelEventProps = TDateToolbarEventProps & { onSelectStart?: TEventCallback onSelectEnd?: TEventCallback onSelecting?: TEventCallback + onToday?: TEventCallback onChange?: (type: TDatePanelType, config: TDateSelectingBase) => void } export type TDatePanelDataProps = TDateToolbarDataProps & TDateSelectingBase -export type TDatePanelProps = TDatePanelDataProps & TDatePanelEventProps +export type TDatePanelProps = { showToday?: boolean; } & TDatePanelDataProps & TDatePanelEventProps export type TProps = ({ diff --git a/devui/date-picker/components/utils.ts b/devui/date-picker/components/utils.ts index cb2c02a57e3d9f1f25dc11efe0b2c125adddc389..23150961ab6f902b807f804df1a5bbd91147257b 100644 --- a/devui/date-picker/components/utils.ts +++ b/devui/date-picker/components/utils.ts @@ -146,4 +146,41 @@ const _parseInt = (str: any, dftVal?: number) => { export const parseTime = (str?: string) : [number, number, number, number] => { const [h, m, s, ms] = str.split(/[\:\.]+/) return [_parseInt(h, 0), _parseInt(m, 0), _parseInt(s, 0), _parseInt(ms, 0)] +} + +type TDateCounterType = 'd' | 'm' | 'y' + +export const compareDateSort = (d1: Date, d2: Date, type: TDateCounterType = 'd') => { + const t1 = dateCounter(d1, type), t2 = dateCounter(d2, type) + return t1 < t2 ? -1 : t1 > t2 ? 1 : 0 +} + +export const dateCounter = (date: Date, type: TDateCounterType) => { + switch(type) { + case 'y': return date.getFullYear() + case 'm': return date.getFullYear() * 12 + date.getMonth() + } + return date.getTime() / ONE_DAY >> 0 +} +export const borderDateFactory = (factor: (d1: Date, d2: Date) => Date) => (...ds: Date[]) => { + return ds.length < 2 ? ds[0] || new Date() : ds.reduce((r, v) => factor(r, v)) +} +export const getMinDate = borderDateFactory((d1: Date, d2: Date) => compareDateSort(d1, d2) < 0 ? d1 : d2) +export const getMaxDate = borderDateFactory((d1: Date, d2: Date) => compareDateSort(d1, d2) < 0 ? d2 : d1) + +/** + * d 是否在 [left, right] 区间 + * @param date 日期 + * @param left 最小日期 + * @param right 最大日期 + * @returns boolean + */ +export const betweenDate = (date: Date, left: any, right: any): boolean => { + if(left instanceof Date && compareDateSort(left, date, 'd') > 0) { + return false + } + if(right instanceof Date && compareDateSort(date, right, 'd') > 0) { + return false + } + return true } \ No newline at end of file diff --git a/devui/date-picker/date-picker.scss b/devui/date-picker/date-picker.scss index 26d48111a251df99f3aacb48adcc067654f0aa39..a577a9938d4883ed621e14f1069fc568d541c82a 100644 --- a/devui/date-picker/date-picker.scss +++ b/devui/date-picker/date-picker.scss @@ -5,17 +5,6 @@ $border-width: 1px; $border-style: solid; $border-color: #dddddd; -.devui-datepicker-global-viewport { - position: fixed; - left: 0; - top: 0; - width: 0; - height: 0; - background-color: rgba(0, 0, 0, 0.2); - z-index: 99; - overflow: visible; -} - .devui-datepicker-container { margin: 0; padding: 0; diff --git a/devui/date-picker/date-picker.tsx b/devui/date-picker/date-picker.tsx index c8285aca5db6294a46a03b07d711d75162b0eaab..e6a66364e0c9a46751576a684459aaab5195531d 100644 --- a/devui/date-picker/date-picker.tsx +++ b/devui/date-picker/date-picker.tsx @@ -1,12 +1,11 @@ -import { defineComponent, reactive, onMounted, onUnmounted, ref } from 'vue' -import { - EventManager, isIn, - traceNode, invokeFunction, -} from './utils' +import type { UnwrapRef } from 'vue' +import { defineComponent, reactive, onMounted } from 'vue' +import { invokeFunction } from './utils' +import { compareDateSort } from './components/utils' +import Popup from './components/popup' import { TState, - handlePositionFactory, handleCalendarSwitchState, formatValue, formatPlaceholder, @@ -18,6 +17,36 @@ import Calendar from './components/calendar' import './date-picker.scss' import { parseDate } from './components/utils' +const formatProps = (props: any): TState => { + const state: TState = { + range: !!props.range, + show: false, + input: props.attachInputDom, + } + state.current = parseDate(props.dateMin) || new Date() + state.next = new Date(state.current.getFullYear(), state.current.getMonth() + 1, 1) + return state +} + +const formatRange = (state: UnwrapRef) => { + const [start, end] = [state.start, state.end].sort((a, b) => a.getTime() - b.getTime()) + + state.start = start + state.end = end + + if (compareDateSort(start, end, 'm') !== 0) { + state.current = start + state.next = end + } else { + if (compareDateSort(start, state.current) < 0) { + state.current = start + } + if (compareDateSort(state.next, end) < 0) { + state.next = end + } + } +} + export default defineComponent({ name: 'DDatepicker', props: { @@ -30,29 +59,14 @@ export default defineComponent({ attachInputDom: { type: String }, dateMin: { type: String }, dateMax: { type: String }, + showToday: { type: Boolean, default: false }, }, setup(props, ctx) { - const container = ref() - const evtman = new EventManager() - const current = new Date() - - const state = reactive({ - range: !!props.range, - current, - next: new Date(current.getFullYear(), current.getMonth() + 1, 1), - show: false, - input: props.attachInputDom, - st: true, - x: '0', - y: '0', - }) - - // 弹出层跟踪 - const handlePosition = handlePositionFactory(state, props, container) + const state = reactive(formatProps(props)) // 绑定层显示值、placehoder值设置 - const setBindingDom = (el: any = getAttachInputDom(state, props)) => { + const setBindingDom = (el: any = getAttachInputDom(props)) => { const value = formatValue(state, props) const placeholder = formatPlaceholder(props) @@ -69,66 +83,21 @@ export default defineComponent({ return value } - const reset = () => { - state.hover = null - state.current = null - state.next = null - if (state.start) { - if (state.end && Math.abs(state.end.getMonth() - state.start.getMonth()) > 0) { - state.next = state.end - } - } else { - state.end = null - } - } - onMounted(() => { - // 获取绑定节点(默认input) - const el = getAttachInputDom(state, props) - // 绑定节点不存在,作为普通组件展示。 - if (!el) { - // 显示组件 - state.show = true - return - } else { - // 作为弹出层,先隐藏 - state.show = false - } - - setBindingDom(el) - - // 绑定节点click事件处理弹出层显示 - evtman.append(el, 'click', () => !state.show && (state.show = true)) - // document层处理`点击其他区域隐藏` - evtman.append(document, 'click', (e: MouseEvent) => { - if (!state.show || e.target === el || isIn(e.target as Node, container.value)) { - return - } - state.show = false - reset() - }) - // 对绑定节点做scroll跟踪,并绑定跟踪事件 - traceNode(el).forEach(node => { - evtman.append(node, 'scroll', handlePosition) - }) - }) - - onUnmounted(() => { - evtman.dispose() + setBindingDom() }) return () => { - handlePosition() - setBindingDom() return ( -
-
+ state.show = true} + onClosed={() => { + state.show = false + }} + > +
{ - state.current = state.end = state.hover = undefined + state.end = state.hover = undefined state.start = date }} onChange={() => { @@ -150,9 +119,26 @@ export default defineComponent({ state.show = false } }} - onSelected={(date: Date) => state.start = date} + onToday={(date: Date) => { + state.current = date + state.start = date + const output = setBindingDom() + invokeFunction(props.selectedDateChange, output) + if (props.autoClose) { + state.show = false + } + }} + onSelected={(date: Date) => { + state.start = date + if (compareDateSort(state.current, date) !== 0) { + state.current = date + } + }} onSelectStart={(date: Date) => state.start = date} - onSelectEnd={(date: Date) => state.end = date} + onSelectEnd={(date: Date) => { + state.end = date + formatRange(state) + }} onSelecting={(date: Date) => state.hover = date} onPreviousYear={(date: Date, pos: number) => handleCalendarSwitchState(state, 0, pos, date)} onPreviousMonth={(date: Date, pos: number) => handleCalendarSwitchState(state, 1, pos, date)} @@ -160,7 +146,7 @@ export default defineComponent({ onNextYear={(date: Date, pos: number) => handleCalendarSwitchState(state, 3, pos, date)} />
-
+ ) } } diff --git a/devui/date-picker/helper.ts b/devui/date-picker/helper.ts index 0bc57373451e73cf5ea77621be18a878322136fb..5b002481c495052c48c87553c82ff24dc9ac173d 100644 --- a/devui/date-picker/helper.ts +++ b/devui/date-picker/helper.ts @@ -10,9 +10,6 @@ export type TState = { hover?: Date show?: boolean input?: string - st?: boolean - x?: string - y?: string } /** @@ -109,8 +106,8 @@ export const handleValue = (id: string | undefined, output: string) => { * 获取绑定节点 * @returns */ -export const getAttachInputDom = (state: TState, props: any) => { - const { attachInputDom } = props || {} +export const getAttachInputDom = (props: any) => { + const { attach, attachInputDom = attach } = props || {} if (!attachInputDom || typeof attachInputDom !== 'string') { return null } @@ -118,7 +115,6 @@ export const getAttachInputDom = (state: TState, props: any) => { if (!el) { return null } - state.st = false return el } @@ -129,14 +125,21 @@ export const getAttachInputDom = (state: TState, props: any) => { * @param container * @returns */ -export const handlePositionFactory = (state: TState, props: any, container: Ref) => () => { +export const handlePositionFactory = (state: { + x?: string + y?: string + attachInputDom?: string + show?: boolean + st?: boolean +}, props: any, container: Ref) => () => { if (!state.show) { state.x = `-100%` state.y = `-100%` return } - const el = getAttachInputDom(state, props) + const el = getAttachInputDom(props) if (!el) { + state.st = true return } const { left, top, width, height } = el.getBoundingClientRect() diff --git a/sites/components/date-picker/index.md b/sites/components/date-picker/index.md index 84dd4a5aac49d5a16c261cb83e9df8c47b620391..3501f359ed24551606dca473712204ac6e0b6252 100644 --- a/sites/components/date-picker/index.md +++ b/sites/components/date-picker/index.md @@ -97,7 +97,7 @@ export default defineComponent({ ### 区间限制
- +
```jsx @@ -118,21 +118,41 @@ export default defineComponent({ 暂定通过`querySelector`查找节点,绑定真实`dom`节点。此方案待定。 ```jsx + + +``` +
- - + +
+ + +```jsx + + ```