From d2d53ea656fea890b678ac1d435551d668ad6044 Mon Sep 17 00:00:00 2001 From: mrundef Date: Thu, 5 Aug 2021 19:23:33 +0800 Subject: [PATCH 01/42] =?UTF-8?q?feat:=20datepicker=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/datepicker.css | 44 +++++++++++++++++++++++ devui/datepicker/datepicker.tsx | 43 ++++++++++++++++++++-- devui/datepicker/demo/datepicker-demo.tsx | 5 +-- 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 devui/datepicker/datepicker.css diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.css new file mode 100644 index 00000000..21114ade --- /dev/null +++ b/devui/datepicker/datepicker.css @@ -0,0 +1,44 @@ +.datapicker-container { + border: 1px solid #000000; + padding: 1em; + position: relative; +} + +.datepicker-input-icon { + width: 16px; + height: 16px; + box-sizing: border-box; + border-radius: 0; + flex-shrink: 0; + flex-grow: 0; + background-color: #cccccc; +} + +.datapicker-input-container { + position: relative; +} + +.datapicker-input-border { + border: 1px solid #526ecc; + padding: 0 5px; + border-radius: 4px; + box-sizing: border-box; + height: 28px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + +.datapicker-input { + border: none; + outline: none; + position: relative; + padding: 0; + line-height: 16px; + height: 16px; + font-size: 12px; + flex-shrink: 1; + flex-grow: 1; + width: 100%; +} diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 1b01bbc0..63cc4980 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,12 +1,51 @@ import { defineComponent } from 'vue' +import './datepicker.css' + +const DataPickerInputIcon = defineComponent({ + name: 'DDatepickerInputIcon', + props: { + marginLeft: { type: Number, default: 0 }, + marginRight: { type: Number, default: 0 }, + }, + setup(props, ctx) { + const marginLeft = props.marginLeft || 0 + const marginRight = props.marginRight || 0 + return () => + } +}) + +const DataPickerInput = defineComponent({ + name: 'DDatepickerInput', + props: { + width: { type: Number, default: 160 } + }, + setup(props, ctx) { + const width = props.width || 160 + return () => { + return ( +
+ + +
+ ) + } + } +}) export default defineComponent({ - name: 'd-datepicker', + name: 'DDatepicker', props: { }, setup(props, ctx) { return () => { - return
devui-datepicker
+ return ( +
+ +
+ ) } } }) \ No newline at end of file diff --git a/devui/datepicker/demo/datepicker-demo.tsx b/devui/datepicker/demo/datepicker-demo.tsx index 995d9cda..b5cd3651 100644 --- a/devui/datepicker/demo/datepicker-demo.tsx +++ b/devui/datepicker/demo/datepicker-demo.tsx @@ -1,12 +1,13 @@ import { defineComponent } from 'vue' +import DatePicker from '../datepicker' export default defineComponent({ - name: 'd-datepicker-demo', + name: 'DDatepickerDemo', props: { }, setup(props, ctx) { return () => { - return
devui-datepicker-demo
+ return } } }) \ No newline at end of file -- Gitee From 05d215fdda81cc650a53f582279232e7466a234c Mon Sep 17 00:00:00 2001 From: mrundef Date: Thu, 5 Aug 2021 19:28:29 +0800 Subject: [PATCH 02/42] =?UTF-8?q?feat:=20=E8=A7=A3=E6=9E=84=E5=86=99?= =?UTF-8?q?=E6=B3=95=E9=99=8D=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/datepicker.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 63cc4980..701e36cc 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -8,8 +8,7 @@ const DataPickerInputIcon = defineComponent({ marginRight: { type: Number, default: 0 }, }, setup(props, ctx) { - const marginLeft = props.marginLeft || 0 - const marginRight = props.marginRight || 0 + const { marginLeft = 0, marginRight = 0 } = props || {} return () => { return (
-- Gitee From 55a15559d73d94810ff7b2e13139b956cbe0ad66 Mon Sep 17 00:00:00 2001 From: mrundef Date: Fri, 6 Aug 2021 11:02:58 +0800 Subject: [PATCH 03/42] =?UTF-8?q?feat:=20=E7=BB=84=E4=BB=B6=E6=8B=86?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/components/icon/index.css | 9 ++ devui/datepicker/components/icon/index.tsx | 17 +++ devui/datepicker/components/input/index.css | 28 +++++ devui/datepicker/components/input/index.tsx | 27 +++++ devui/datepicker/datepicker.css | 41 +------ devui/datepicker/datepicker.tsx | 112 ++++++++++++++------ 6 files changed, 162 insertions(+), 72 deletions(-) create mode 100644 devui/datepicker/components/icon/index.css create mode 100644 devui/datepicker/components/icon/index.tsx create mode 100644 devui/datepicker/components/input/index.css create mode 100644 devui/datepicker/components/input/index.tsx diff --git a/devui/datepicker/components/icon/index.css b/devui/datepicker/components/icon/index.css new file mode 100644 index 00000000..51380477 --- /dev/null +++ b/devui/datepicker/components/icon/index.css @@ -0,0 +1,9 @@ +.datepicker-input-icon { + width: 16px; + height: 16px; + box-sizing: border-box; + border-radius: 0; + flex-shrink: 0; + flex-grow: 0; + background-color: #cccccc; +} diff --git a/devui/datepicker/components/icon/index.tsx b/devui/datepicker/components/icon/index.tsx new file mode 100644 index 00000000..938b91ad --- /dev/null +++ b/devui/datepicker/components/icon/index.tsx @@ -0,0 +1,17 @@ +import { defineComponent } from 'vue' +import './index.css' + +export default defineComponent({ + name: 'DDatepickerInputIcon', + props: { + marginLeft: { type: Number, default: 0 }, + marginRight: { type: Number, default: 0 }, + }, + setup(props, ctx) { + const { marginLeft = 0, marginRight = 0 } = props || {} + return () => + } +}) \ No newline at end of file diff --git a/devui/datepicker/components/input/index.css b/devui/datepicker/components/input/index.css new file mode 100644 index 00000000..0707f8c3 --- /dev/null +++ b/devui/datepicker/components/input/index.css @@ -0,0 +1,28 @@ +.datapicker-input-container { + position: relative; +} + +.datapicker-input-border { + border: 1px solid #526ecc; + padding: 0 5px; + border-radius: 4px; + box-sizing: border-box; + height: 28px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + +.datapicker-input { + border: none; + outline: none; + position: relative; + padding: 0; + line-height: 16px; + height: 16px; + font-size: 12px; + flex-shrink: 1; + flex-grow: 1; + width: 100%; +} diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx new file mode 100644 index 00000000..2894bf92 --- /dev/null +++ b/devui/datepicker/components/input/index.tsx @@ -0,0 +1,27 @@ +import { defineComponent } from 'vue' +import DataPickerInputIcon from '../icon/index' + +import './index.css' + +export default defineComponent({ + name: 'DDatepickerInput', + props: { + width: { type: Number, default: 160 }, + onActive: { type: Function, default: null }, + }, + setup(props, ctx) { + const { width = 160, onActive } = props || {} + return () => { + return ( +
+ + +
+ ) + } + } +}) \ No newline at end of file diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.css index 21114ade..ed2c0d0c 100644 --- a/devui/datepicker/datepicker.css +++ b/devui/datepicker/datepicker.css @@ -1,44 +1,5 @@ .datapicker-container { border: 1px solid #000000; - padding: 1em; + padding: 5px; position: relative; } - -.datepicker-input-icon { - width: 16px; - height: 16px; - box-sizing: border-box; - border-radius: 0; - flex-shrink: 0; - flex-grow: 0; - background-color: #cccccc; -} - -.datapicker-input-container { - position: relative; -} - -.datapicker-input-border { - border: 1px solid #526ecc; - padding: 0 5px; - border-radius: 4px; - box-sizing: border-box; - height: 28px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; -} - -.datapicker-input { - border: none; - outline: none; - position: relative; - padding: 0; - line-height: 16px; - height: 16px; - font-size: 12px; - flex-shrink: 1; - flex-grow: 1; - width: 100%; -} diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 701e36cc..65fbe38f 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,48 +1,96 @@ -import { defineComponent } from 'vue' +import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' +import Input from './components/input/index' +import PopPanel from './components/pop-panel/index' + import './datepicker.css' -const DataPickerInputIcon = defineComponent({ - name: 'DDatepickerInputIcon', - props: { - marginLeft: { type: Number, default: 0 }, - marginRight: { type: Number, default: 0 }, - }, - setup(props, ctx) { - const { marginLeft = 0, marginRight = 0 } = props || {} - return () => - } -}) +/** + * 对`getBoundingClientRect`获取到的DOMRect做浅拷贝 + * @param rect + * @returns + */ +const cloneDOMRect = (rect: DOMRect) => { + const { + left, top, width, height, right = left + width, bottom = top + height, x, y + } = rect + return { x, y, left, top, width, height, right, bottom } +} -const DataPickerInput = defineComponent({ - name: 'DDatepickerInput', - props: { - width: { type: Number, default: 160 } - }, - setup(props, ctx) { - const { width = 160 } = props || {} - return () => { - return ( -
- - -
- ) +const isIn = (start: Node | null, target: Node | null) => { + if(!target) { + return false + } + while(start) { + if(start === target) { + return true } + start = start.parentNode + } + return false +} + +const factoryAutoClosePanel = (cont: HTMLElement, cb: () => void) => (e: MouseEvent) => { + const { target } = e + if(isIn(target as Node, cont)) { + return } -}) + cb() +} + +const attachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { + el.addEventListener(name, cb, capture) + return { el, name, cb, capture } +} + +const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { + el.removeEventListener(name, cb, capture) +} + export default defineComponent({ name: 'DDatepicker', props: { }, setup(props, ctx) { + const container = ref() + const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] + + const state = reactive({ + showPanel: false + }) + + onMounted(() => { + const { value: cont } = container + if(!cont) { + return + } + const rect = cloneDOMRect(cont.getBoundingClientRect()) + const handleAutoClosePanel = factoryAutoClosePanel(cont, () => { + state.showPanel = false + }) + events.push(attachEvent(document, 'click', handleAutoClosePanel, false)) + // // 窗口失焦点时隐藏弹窗 + // events.push(attachEvent(window, 'blur', () => { state.showPanel = false }, false)) + }) + + onUnmounted(() => { + events.forEach(({ el, cb, name, capture }) => detachEvent(el, name, cb, capture)) + events.splice(0, events.length) + }) + return () => { + + const handleActive = (e: MouseEvent) => { + state.showPanel = true + } + return ( -
- +
+ +
) } -- Gitee From 126d91af4abdbcc6c3cfa356e006cb20ed997b6a Mon Sep 17 00:00:00 2001 From: mrundef Date: Fri, 6 Aug 2021 11:04:59 +0800 Subject: [PATCH 04/42] feat: pop-panel init --- .../datepicker/components/pop-panel/index.css | 4 +++ .../datepicker/components/pop-panel/index.tsx | 18 +++++++++++++ devui/datepicker/demo/datepicker-demo.tsx | 27 ++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 devui/datepicker/components/pop-panel/index.css create mode 100644 devui/datepicker/components/pop-panel/index.tsx diff --git a/devui/datepicker/components/pop-panel/index.css b/devui/datepicker/components/pop-panel/index.css new file mode 100644 index 00000000..78b7d64e --- /dev/null +++ b/devui/datepicker/components/pop-panel/index.css @@ -0,0 +1,4 @@ +.datapicker-pop-panel { + display: block; + position: absolute; +} diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx new file mode 100644 index 00000000..d1e86d81 --- /dev/null +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -0,0 +1,18 @@ +import { defineComponent } from 'vue' + +import './index.css' + +export default defineComponent({ + name: 'DDatepickerPopPanel', + props: { + show: { type: Boolean, default: true }, + }, + setup(props) { + return() => { + if(!props.show) { + return null + } + return (
POP PANEL
) + } + } + }) \ No newline at end of file diff --git a/devui/datepicker/demo/datepicker-demo.tsx b/devui/datepicker/demo/datepicker-demo.tsx index b5cd3651..22168b91 100644 --- a/devui/datepicker/demo/datepicker-demo.tsx +++ b/devui/datepicker/demo/datepicker-demo.tsx @@ -7,7 +7,32 @@ export default defineComponent({ }, setup(props, ctx) { return () => { - return + return ( +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ ) } } }) \ No newline at end of file -- Gitee From c8a1d6b9f77793f4e79e6912b2e84a2630b20944 Mon Sep 17 00:00:00 2001 From: mrundef Date: Fri, 6 Aug 2021 14:37:42 +0800 Subject: [PATCH 05/42] =?UTF-8?q?feat:=20PopPanel=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E4=BD=8D=E7=BD=AE=E8=87=AA=E9=80=82=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/components/input/index.tsx | 17 +++- .../datepicker/components/pop-panel/index.css | 1 + .../datepicker/components/pop-panel/index.tsx | 64 +++++++++++---- devui/datepicker/datepicker.css | 12 ++- devui/datepicker/datepicker.tsx | 81 ++++++++++++++++--- devui/datepicker/demo/datepicker-demo.tsx | 71 ++++++++++++++++ 6 files changed, 217 insertions(+), 29 deletions(-) diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx index 2894bf92..0539dc33 100644 --- a/devui/datepicker/components/input/index.tsx +++ b/devui/datepicker/components/input/index.tsx @@ -1,22 +1,31 @@ -import { defineComponent } from 'vue' +import { defineComponent, ref } from 'vue' import DataPickerInputIcon from '../icon/index' import './index.css' +type TEventCallback = (e: MouseEvent) => void + export default defineComponent({ name: 'DDatepickerInput', props: { - width: { type: Number, default: 160 }, - onActive: { type: Function, default: null }, + width: { type: Number }, + onActive: { type: Function }, }, setup(props, ctx) { const { width = 160, onActive } = props || {} + const container = ref() + const handleClick = (e: MouseEvent) => { + if(container.value && typeof onActive === 'function') { + onActive(container.value) + } + } return () => { return (
diff --git a/devui/datepicker/components/pop-panel/index.css b/devui/datepicker/components/pop-panel/index.css index 78b7d64e..7cc095d0 100644 --- a/devui/datepicker/components/pop-panel/index.css +++ b/devui/datepicker/components/pop-panel/index.css @@ -1,4 +1,5 @@ .datapicker-pop-panel { display: block; position: absolute; + box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2); } diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx index d1e86d81..d69ce0ba 100644 --- a/devui/datepicker/components/pop-panel/index.tsx +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -1,18 +1,54 @@ -import { defineComponent } from 'vue' +import { defineComponent, ref, reactive, onUpdated, onBeforeUpdate, SetupContext, EmitsOptions } from 'vue' import './index.css' +type TProps = { + show: boolean + background?: string + border?: string + padding?: string + hostArea?: any + xPosition?: 'left' | 'right' + yPosition?: 'top' | 'bottom' +} + export default defineComponent({ - name: 'DDatepickerPopPanel', - props: { - show: { type: Boolean, default: true }, - }, - setup(props) { - return() => { - if(!props.show) { - return null - } - return (
POP PANEL
) - } - } - }) \ No newline at end of file + name: 'DDatepickerPopPanel', + props: { + show: { type: Boolean }, + xPosition: { type: String }, + yPosition: { type: String }, + xOffset: { type: Number }, + yOffset: { type: Number }, + children: { type: Object } + }, + setup(props) { + + console.log(props) + + const container = ref() + + return () => { + if (!props.show) { + return null + } + const { + xPosition = 'left', yPosition = 'top', + xOffset = 0, yOffset = 0, + children = null, + } = props + + return ( +
{children}
) + } + } +}) \ No newline at end of file diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.css index ed2c0d0c..f1ad1756 100644 --- a/devui/datepicker/datepicker.css +++ b/devui/datepicker/datepicker.css @@ -1,5 +1,13 @@ .datapicker-container { - border: 1px solid #000000; - padding: 5px; + margin: 0; + padding: 0; position: relative; } + +.datepicker-pop-container { + position: absolute; + width: 0; + height: 0; + left: 0; + top: 0; +} diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 65fbe38f..0b1fe6c7 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -29,7 +29,7 @@ const isIn = (start: Node | null, target: Node | null) => { return false } -const factoryAutoClosePanel = (cont: HTMLElement, cb: () => void) => (e: MouseEvent) => { +const factoryAutoClosePanel = (cont: Element, cb: () => void) => (e: MouseEvent) => { const { target } = e if(isIn(target as Node, cont)) { return @@ -46,17 +46,53 @@ const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capt el.removeEventListener(name, cb, capture) } +const getScrollOffset = (el: Element) => { + let x = 0, y = 0 + while(el.parentElement) { + el = el.parentElement + x += el.scrollLeft + y += el.scrollTop + } + return [x, y] +} + +const getHostRange = (host: Element): { + left: number + right: number + top: number + bottom: number + width: number + height: number +} => { + const { left, top, width, height } = host.getBoundingClientRect() + const right = window.innerWidth - left - width + const bottom = window.innerHeight - top - height + + // console.log(left, right, top, bottom) + return { left, right, top, bottom, width, height } +} export default defineComponent({ name: 'DDatepicker', props: { }, setup(props, ctx) { - const container = ref() + const container = ref() + const popCont = ref() const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] - const state = reactive({ - showPanel: false + const state = reactive<{ + showPanel: boolean + panelXPos: 'left' | 'right' + panelYPos: 'top' | 'bottom' + pointX: string + pointY: string + }>({ + showPanel: false, + panelXPos: 'left', + panelYPos: 'top', + pointX: '0px', + pointY: '0px', }) onMounted(() => { @@ -64,7 +100,7 @@ export default defineComponent({ if(!cont) { return } - const rect = cloneDOMRect(cont.getBoundingClientRect()) + const handleAutoClosePanel = factoryAutoClosePanel(cont, () => { state.showPanel = false }) @@ -78,19 +114,46 @@ export default defineComponent({ events.splice(0, events.length) }) - return () => { + const handleActive = (e: Element) => { + if(state.showPanel) { + return + } + const range = getHostRange(e) + if(range.left > range.right) { + state.panelXPos = 'right' + state.pointX = `${range.width}px` + } else { + state.panelXPos = 'left' + state.pointX = '0px' + } - const handleActive = (e: MouseEvent) => { - state.showPanel = true + if(range.top > range.bottom) { + state.panelYPos = 'bottom' + state.pointY = '0px' + } else { + state.panelYPos = 'top' + state.pointY = `${range.height}px` } + state.showPanel = true + } + return () => { return (
- +
+ Hello Pop Panel
} + /> +
) } diff --git a/devui/datepicker/demo/datepicker-demo.tsx b/devui/datepicker/demo/datepicker-demo.tsx index 22168b91..431cfb7c 100644 --- a/devui/datepicker/demo/datepicker-demo.tsx +++ b/devui/datepicker/demo/datepicker-demo.tsx @@ -29,6 +29,77 @@ export default defineComponent({


+

+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+

-- Gitee From 9fc968499dbbcdfaa5c33f498ef864e43dc08637 Mon Sep 17 00:00:00 2001 From: mrundef Date: Fri, 6 Aug 2021 18:50:50 +0800 Subject: [PATCH 06/42] =?UTF-8?q?feat:=20=E6=97=A5=E5=8E=86=E4=B8=8E?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datepicker/components/calendar/index.css | 66 +++++++ .../datepicker/components/calendar/index.tsx | 167 ++++++++++++++++++ .../datepicker/components/pop-panel/index.css | 1 - .../datepicker/components/pop-panel/index.tsx | 4 +- devui/datepicker/datepicker.tsx | 14 +- 5 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 devui/datepicker/components/calendar/index.css create mode 100644 devui/datepicker/components/calendar/index.tsx diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css new file mode 100644 index 00000000..2d8af308 --- /dev/null +++ b/devui/datepicker/components/calendar/index.css @@ -0,0 +1,66 @@ +.calendar-container { + border: 0 solid #aaaaaa; +} + +.calendar { + border: 1px solid #dddddd; + padding: 5px; + width: 240px; + box-sizing: border-box; +} + +.calendar-row { + display: flex; + flex-direction: row; + justify-content: space-between; + height: 28px; +} + +.calendar-cell { + width: 100%; + text-align: center; + flex-grow: 1; + flex-shrink: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.calendar-cell:hover { + background-color: #eeeeee; +} + +.calendar-cell.selected { + background-color: #0066cc; + color: #ffffff; + border-radius: 4px; +} + +.calendar-cell.innerday { + background-color: #eeeeee; +} + +.calenday-cell-text { + font-weight: normal; + font-style: normal; + font-size: 12px; + text-align: center; +} + +.calendar-header { + background-color: #0066cc; + color: #ffffff; + height: 32px; + cursor: pointer; +} + +.calendar-header .calendar-cell:hover { + background-color: #0033cc; +} + +.calendar-body { + display: flex; + flex-direction: column; + cursor: pointer; +} diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx new file mode 100644 index 00000000..828f32be --- /dev/null +++ b/devui/datepicker/components/calendar/index.tsx @@ -0,0 +1,167 @@ +import { defineComponent, reactive } from 'vue' + +import './index.css' + +const getHumanDate = (d: Date) => { + const year = d.getFullYear() + const month = d.getMonth() + 1 + const date = d.getDate() + const day = d.getDay() + const hour = d.getHours() + const minute = d.getMinutes() + const second = d.getSeconds() + const ms = d.getMilliseconds() + + return { + year, y: year, month, M: month, date, d: date, day, + hour, H: hour, h: hour, + minute, m: minute, + second, s: second, + ms, + } +} + +const getMonthDays = (year: number, month: number) => { + const first = new Date(year, month - 1, 1) + const last = new Date(year, month, 0) + const dates: Date[] = [] + + let day = first.getDay() + while(day > 0) { + day -= 1 + dates.push(new Date(year, month - 1, -day)) + } + + day = last.getDate() - first.getDate() + for(let i = 0; i <= day; i++) { + const date = new Date(first) + date.setDate(i + 1) + dates.push(date) + } + + day = last.getDay() + for(let i = day; i < 6; i++) { + dates.push(new Date(year, month, i - day + 1)) + } + return dates +} + +const getWeeklyDays = (date: Date = new Date()) => { + const { year, month } = getHumanDate(date) + const days = getMonthDays(year, month) + const dayRows = [] + while(days.length > 0) { + dayRows.push(days.splice(0, 7)) + } + return dayRows +} + +const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'] + +export default defineComponent({ + name: 'DDatepickerCalendar', + props: { + mode: { type: String } + }, + setup(props) { + + const state = reactive<{ + dateStartKey: string + dateStart: Date | null + dateHoverKey: string + dateHover: Date | null + dateEndKey: string + dateEnd: Date | null + }>({ + dateStartKey: '', + dateStart: null, + dateHoverKey: '', + dateHover: null, + dateEndKey: '', + dateEnd: null, + }) + + const handleDateClick = (key: string, day: Date) => { + if(props.mode === 'range') { + if(!state.dateStart) { + state.dateStart = day + state.dateStartKey = key + state.dateEnd = null + state.dateEndKey = '' + } else if(!state.dateEnd) { + state.dateEnd = day + state.dateEndKey = key + } else { + state.dateStart = null + state.dateStartKey = '' + state.dateHover = day + state.dateHoverKey = key + state.dateEnd = null + state.dateEndKey = '' + handleDateClick(key, day) + } + } else { + state.dateStart = day + state.dateStartKey = key + state.dateEnd = null + state.dateEndKey = '' + } + } + + const handleDateEnter = (key: string, day: Date) => { + if(props.mode === 'range') { + if(!state.dateStart || state.dateStartKey === key || state.dateEnd) { + return + } + state.dateHover = day + state.dateHoverKey = key + } + } + + const cellClassName = (key: string, day: Date) => { + if(props.mode === 'range') { + if(state.dateStart) { + if(state.dateStartKey === key || state.dateEndKey === key) { + return `calendar-cell selected` + } + const innerEnd = state.dateEnd || state.dateHover + if(innerEnd) { + const range = innerEnd > state.dateStart ? [state.dateStart, innerEnd] : [innerEnd, state.dateStart] + if(day > range[0] && day < range[1]) { + return `calendar-cell innerday` + } + } + } + return `calendar-cell` + } else { + return state.dateStartKey === key ? `calendar-cell selected` : `calendar-cell` + } + } + + return () => { + return ( +
+
+
    { + WEEK_DAYS.map((day, idx) =>
  1. {day}
  2. ) + }
+
    { + getWeeklyDays().map((row, idx0) =>
  • { + row.map((day, idx1) => { + const key = `${idx0}_${idx1}` + return ( + handleDateClick(key, day)} + onMouseenter={() => handleDateEnter(key, day)} + >{day.getDate()} + ) + }) + }
  • ) + }
+
+
+ ) + } + } +}) \ No newline at end of file diff --git a/devui/datepicker/components/pop-panel/index.css b/devui/datepicker/components/pop-panel/index.css index 7cc095d0..78b7d64e 100644 --- a/devui/datepicker/components/pop-panel/index.css +++ b/devui/datepicker/components/pop-panel/index.css @@ -1,5 +1,4 @@ .datapicker-pop-panel { display: block; position: absolute; - box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2); } diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx index d69ce0ba..91915038 100644 --- a/devui/datepicker/components/pop-panel/index.tsx +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -1,4 +1,4 @@ -import { defineComponent, ref, reactive, onUpdated, onBeforeUpdate, SetupContext, EmitsOptions } from 'vue' +import { defineComponent, ref } from 'vue' import './index.css' @@ -24,8 +24,6 @@ export default defineComponent({ }, setup(props) { - console.log(props) - const container = ref() return () => { diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 0b1fe6c7..fc3e55e7 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,6 +1,7 @@ import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' import Input from './components/input/index' import PopPanel from './components/pop-panel/index' +import Calendar from './components/calendar/index' import './datepicker.css' @@ -146,12 +147,19 @@ export default defineComponent({
Hello Pop Panel
} + xOffset={0} + yOffset={0} + children={ +
+ } />
-- Gitee From fc2def9cccb980d4e5f465c364725c3b327605d9 Mon Sep 17 00:00:00 2001 From: Marvin Date: Sat, 7 Aug 2021 12:17:53 +0800 Subject: [PATCH 07/42] =?UTF-8?q?feat:=20=E6=A8=A1=E5=9D=97=E5=8C=96?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=B8=8E=E5=A4=9A=E6=97=A5=E6=9C=9F=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E8=81=94=E5=90=88=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datepicker/components/calendar/helper.ts | 79 +++++++ .../datepicker/components/calendar/index.css | 11 +- .../datepicker/components/calendar/index.tsx | 215 +++++------------- devui/datepicker/components/calendar/utils.ts | 64 ++++++ .../datepicker/components/pop-panel/index.tsx | 11 +- devui/datepicker/datepicker.tsx | 39 ++-- 6 files changed, 233 insertions(+), 186 deletions(-) create mode 100644 devui/datepicker/components/calendar/helper.ts create mode 100644 devui/datepicker/components/calendar/utils.ts diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts new file mode 100644 index 00000000..ce4d2519 --- /dev/null +++ b/devui/datepicker/components/calendar/helper.ts @@ -0,0 +1,79 @@ +import { invokeCallback, TDateCell } from './utils' + +export type TEventCallback = (date: Date) => void + +export type TProps = { + mode: 'select' | 'range' + type: 'month' | 'year' + current?: Date + next?: Date + dateStart?: Date + dateHover?: Date + dateEnd?: Date + onSelected?: TEventCallback + onReset?: TEventCallback + onSelectStart?: TEventCallback + onSelectEnd?: TEventCallback + onSelecting?: TEventCallback +} + +export const getDateKey = (date: Date) => { + return date.toDateString() +} + +export const cellClassName = (props: TProps, day: TDateCell) => { + if(day.current !== 0) { + return 'calendar-cell disabled' + } + const key = getDateKey(day.date) + if (props.mode === 'range') { + if (props.dateStart) { + if (getDateKey(props.dateStart) === key) { + return `calendar-cell selected` + } + if (props.dateEnd && getDateKey(props.dateEnd) === key) { + return `calendar-cell selected` + } + const innerEnd = props.dateEnd || props.dateHover + if (innerEnd) { + const range = innerEnd > props.dateStart ? [props.dateStart, innerEnd] : [innerEnd, props.dateStart] + if (day.date > range[0] && day.date < range[1]) { + return `calendar-cell innerday` + } + } + } + return `calendar-cell` + } else { + return props.dateStart && getDateKey(props.dateStart) === key ? `calendar-cell selected` : `calendar-cell` + } +} + +export const trigEvent = (props: TProps, day: TDateCell) => { + if(day.current !== 0) { + return + } + if (props.mode === 'range') { + if (!props.dateStart) { + invokeCallback(props.onSelectStart, day.date) + } else if (!props.dateEnd) { + invokeCallback(props.onSelectEnd, day.date) + } else { + invokeCallback(props.onReset, day.date) + } + } else { + invokeCallback(props.onSelected, day.date) + } +} + +export const handleDateEnter = (props: TProps, day: TDateCell) => { + if(day.current !== 0) { + return + } + if (props.mode === 'range') { + const key = getDateKey(day.date) + if (!props.dateStart || getDateKey(props.dateStart) === key || props.dateEnd) { + return + } + invokeCallback(props.onSelecting, day.date) + } +} \ No newline at end of file diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css index 2d8af308..92cc9c90 100644 --- a/devui/datepicker/components/calendar/index.css +++ b/devui/datepicker/components/calendar/index.css @@ -1,5 +1,9 @@ .calendar-container { border: 0 solid #aaaaaa; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; } .calendar { @@ -38,7 +42,12 @@ } .calendar-cell.innerday { - background-color: #eeeeee; + background-color: #f1f1f1; +} + +.calendar-cell.disabled { + background-color: #ffffff; + color: #bbbbbb; } .calenday-cell-text { diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 828f32be..38dde082 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,167 +1,62 @@ -import { defineComponent, reactive } from 'vue' +import { defineComponent } from 'vue' +import { getMonthWeeklyDays, WEEK_DAYS } from './utils' +import { TProps, handleDateEnter, cellClassName, trigEvent } from './helper' import './index.css' -const getHumanDate = (d: Date) => { - const year = d.getFullYear() - const month = d.getMonth() + 1 - const date = d.getDate() - const day = d.getDay() - const hour = d.getHours() - const minute = d.getMinutes() - const second = d.getSeconds() - const ms = d.getMilliseconds() - - return { - year, y: year, month, M: month, date, d: date, day, - hour, H: hour, h: hour, - minute, m: minute, - second, s: second, - ms, - } -} - -const getMonthDays = (year: number, month: number) => { - const first = new Date(year, month - 1, 1) - const last = new Date(year, month, 0) - const dates: Date[] = [] - - let day = first.getDay() - while(day > 0) { - day -= 1 - dates.push(new Date(year, month - 1, -day)) - } - - day = last.getDate() - first.getDate() - for(let i = 0; i <= day; i++) { - const date = new Date(first) - date.setDate(i + 1) - dates.push(date) - } - - day = last.getDay() - for(let i = day; i < 6; i++) { - dates.push(new Date(year, month, i - day + 1)) - } - return dates -} - -const getWeeklyDays = (date: Date = new Date()) => { - const { year, month } = getHumanDate(date) - const days = getMonthDays(year, month) - const dayRows = [] - while(days.length > 0) { - dayRows.push(days.splice(0, 7)) - } - return dayRows +const renderPanel = (date: Date, props: TProps) => { + return ( +
+
    { + WEEK_DAYS.map((day, idx) =>
  1. {day}
  2. ) + }
+
    { + getMonthWeeklyDays(date).map((row, idx0) =>
  • { + row.map((day, idx1) => { + return ( + trigEvent(props, day)} + onMouseenter={() => handleDateEnter(props, day)} + >{day.date.getDate()} + ) + }) + }
  • ) + }
+
+ ) } -const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'] - export default defineComponent({ - name: 'DDatepickerCalendar', - props: { - mode: { type: String } - }, - setup(props) { - - const state = reactive<{ - dateStartKey: string - dateStart: Date | null - dateHoverKey: string - dateHover: Date | null - dateEndKey: string - dateEnd: Date | null - }>({ - dateStartKey: '', - dateStart: null, - dateHoverKey: '', - dateHover: null, - dateEndKey: '', - dateEnd: null, - }) - - const handleDateClick = (key: string, day: Date) => { - if(props.mode === 'range') { - if(!state.dateStart) { - state.dateStart = day - state.dateStartKey = key - state.dateEnd = null - state.dateEndKey = '' - } else if(!state.dateEnd) { - state.dateEnd = day - state.dateEndKey = key - } else { - state.dateStart = null - state.dateStartKey = '' - state.dateHover = day - state.dateHoverKey = key - state.dateEnd = null - state.dateEndKey = '' - handleDateClick(key, day) - } - } else { - state.dateStart = day - state.dateStartKey = key - state.dateEnd = null - state.dateEndKey = '' - } - } - - const handleDateEnter = (key: string, day: Date) => { - if(props.mode === 'range') { - if(!state.dateStart || state.dateStartKey === key || state.dateEnd) { - return - } - state.dateHover = day - state.dateHoverKey = key - } - } - - const cellClassName = (key: string, day: Date) => { - if(props.mode === 'range') { - if(state.dateStart) { - if(state.dateStartKey === key || state.dateEndKey === key) { - return `calendar-cell selected` - } - const innerEnd = state.dateEnd || state.dateHover - if(innerEnd) { - const range = innerEnd > state.dateStart ? [state.dateStart, innerEnd] : [innerEnd, state.dateStart] - if(day > range[0] && day < range[1]) { - return `calendar-cell innerday` - } - } - } - return `calendar-cell` - } else { - return state.dateStartKey === key ? `calendar-cell selected` : `calendar-cell` - } - } - - return () => { - return ( -
-
-
    { - WEEK_DAYS.map((day, idx) =>
  1. {day}
  2. ) - }
-
    { - getWeeklyDays().map((row, idx0) =>
  • { - row.map((day, idx1) => { - const key = `${idx0}_${idx1}` - return ( - handleDateClick(key, day)} - onMouseenter={() => handleDateEnter(key, day)} - >{day.getDate()} - ) - }) - }
  • ) - }
-
-
- ) - } - } + name: 'DDatepickerCalendar', + props: { + mode: { type: String }, + current: { type: Date }, + next: { type: Date }, + dateStart: { type: Date }, + dateHover: { type: Date }, + dateEnd: { type: Date }, + onSelected: { type: Function }, + onReset: { type: Function }, + onSelectStart: { type: Function }, + onSelectEnd: { type: Function }, + onSelecting: { type: Function }, + }, + setup(props) { + return () => { + let { current, next } = props || {} + if (!(current instanceof Date)) { + current = new Date + } + if(!(next instanceof Date)) { + next = new Date(current.getFullYear(), current.getMonth() + 1, 1) + } + return ( +
+ {renderPanel(current, props as TProps)} + {props.mode === 'range' ? renderPanel(next, props as TProps) : null} +
+ ) + } + } }) \ No newline at end of file diff --git a/devui/datepicker/components/calendar/utils.ts b/devui/datepicker/components/calendar/utils.ts new file mode 100644 index 00000000..6410d095 --- /dev/null +++ b/devui/datepicker/components/calendar/utils.ts @@ -0,0 +1,64 @@ +const getHumanDate = (d: Date) => { + const year = d.getFullYear() + const month = d.getMonth() + 1 + const date = d.getDate() + const day = d.getDay() + const hour = d.getHours() + const minute = d.getMinutes() + const second = d.getSeconds() + const ms = d.getMilliseconds() + + return { + year, y: year, month, M: month, date, d: date, day, + hour, H: hour, h: hour, + minute, m: minute, + second, s: second, + ms, + } +} + +export type TDateCell = { date: Date; current: -1 | 0 | 1; } + +const getMonthDays = (year: number, month: number) => { + const first = new Date(year, month - 1, 1) + const last = new Date(year, month, 0) + const dates: TDateCell[] = [] + + let day = first.getDay() + while (day > 0) { + day -= 1 + dates.push({ date: new Date(year, month - 1, -day), current: -1 }) + } + + day = last.getDate() - first.getDate() + for (let i = 0; i <= day; i++) { + const date = new Date(first) + date.setDate(i + 1) + dates.push({ date, current: 0 }) + } + + day = last.getDay() + for (let i = day; i < 6; i++) { + dates.push({ date: new Date(year, month, i - day + 1), current: 1 }) + } + return dates +} + +export const getMonthWeeklyDays = (date: any = new Date()) => { + if(!(date instanceof Date)) { + date = new Date() + } + const { year, month } = getHumanDate(date) + const days = getMonthDays(year, month) + const dayRows = [] + while (days.length > 0) { + dayRows.push(days.splice(0, 7)) + } + return dayRows +} + +export const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'] + +export const invokeCallback = (cb: any, ...args: any[]) => { + typeof cb === 'function' && cb(...args) +} \ No newline at end of file diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx index 91915038..47f68b76 100644 --- a/devui/datepicker/components/pop-panel/index.tsx +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -1,4 +1,4 @@ -import { defineComponent, ref } from 'vue' +import { defineComponent, ref, renderSlot } from 'vue' import './index.css' @@ -22,20 +22,23 @@ export default defineComponent({ yOffset: { type: Number }, children: { type: Object } }, - setup(props) { - + setup(props, ctx) { + const container = ref() return () => { if (!props.show) { return null } + + const { xPosition = 'left', yPosition = 'top', xOffset = 0, yOffset = 0, - children = null, } = props + const children: any = renderSlot(ctx.slots, 'default') + return (
{ - const { - left, top, width, height, right = left + width, bottom = top + height, x, y - } = rect - return { x, y, left, top, width, height, right, bottom } -} - const isIn = (start: Node | null, target: Node | null) => { if(!target) { return false @@ -88,6 +76,9 @@ export default defineComponent({ panelYPos: 'top' | 'bottom' pointX: string pointY: string + dateStart?: Date + dateEnd?: Date + dateHover?: Date }>({ showPanel: false, panelXPos: 'left', @@ -139,6 +130,7 @@ export default defineComponent({ } return () => { + return (
- } - /> + > { + state.dateEnd = state.dateHover = undefined + state.dateStart = date + }} + onSelected={(date: Date) => state.dateStart = date} + onSelectStart={(date: Date) => state.dateStart = date} + onSelectEnd={(date: Date) => state.dateEnd = date} + onSelecting={(date: Date) => state.dateHover = date} + />
) -- Gitee From a7272590312954c6fb6fc7729c48e0902e307d53 Mon Sep 17 00:00:00 2001 From: Marvin Date: Sat, 7 Aug 2021 22:09:28 +0800 Subject: [PATCH 08/42] =?UTF-8?q?feat:=20=E6=97=A5=E6=9C=9F=E5=8C=BA?= =?UTF-8?q?=E9=97=B4=E9=80=89=E6=8B=A9=E6=97=B6=E5=AF=B9=E5=B9=B4=E6=9C=88?= =?UTF-8?q?=E7=9A=84=E6=8C=89=E9=92=AE=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datepicker/components/calendar/helper.ts | 25 ++-- .../datepicker/components/calendar/index.css | 55 +++++++-- .../datepicker/components/calendar/index.tsx | 69 ++++++++++- devui/datepicker/datepicker.tsx | 115 +++++++++++------- 4 files changed, 197 insertions(+), 67 deletions(-) diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts index ce4d2519..afed1baf 100644 --- a/devui/datepicker/components/calendar/helper.ts +++ b/devui/datepicker/components/calendar/helper.ts @@ -1,12 +1,15 @@ import { invokeCallback, TDateCell } from './utils' -export type TEventCallback = (date: Date) => void +export type TEventCallback = (date: Date, position?: number) => void -export type TProps = { - mode: 'select' | 'range' - type: 'month' | 'year' - current?: Date - next?: Date +export type TProps = ({ + type: 'select' +} | { + type: 'range' + next: Date +}) & { + current: Date + mode: 'month' | 'year' dateStart?: Date dateHover?: Date dateEnd?: Date @@ -15,6 +18,10 @@ export type TProps = { onSelectStart?: TEventCallback onSelectEnd?: TEventCallback onSelecting?: TEventCallback + onPreviousYear?: TEventCallback + onPreviousMonth?: TEventCallback + onNextMonth?: TEventCallback + onNextYear?: TEventCallback } export const getDateKey = (date: Date) => { @@ -26,7 +33,7 @@ export const cellClassName = (props: TProps, day: TDateCell) => { return 'calendar-cell disabled' } const key = getDateKey(day.date) - if (props.mode === 'range') { + if (props.type === 'range') { if (props.dateStart) { if (getDateKey(props.dateStart) === key) { return `calendar-cell selected` @@ -52,7 +59,7 @@ export const trigEvent = (props: TProps, day: TDateCell) => { if(day.current !== 0) { return } - if (props.mode === 'range') { + if (props.type === 'range') { if (!props.dateStart) { invokeCallback(props.onSelectStart, day.date) } else if (!props.dateEnd) { @@ -69,7 +76,7 @@ export const handleDateEnter = (props: TProps, day: TDateCell) => { if(day.current !== 0) { return } - if (props.mode === 'range') { + if (props.type === 'range') { const key = getDateKey(day.date) if (!props.dateStart || getDateKey(props.dateStart) === key || props.dateEnd) { return diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css index 92cc9c90..b3538df2 100644 --- a/devui/datepicker/components/calendar/index.css +++ b/devui/datepicker/components/calendar/index.css @@ -31,14 +31,14 @@ justify-content: center; } -.calendar-cell:hover { +.calendar-body .calendar-cell:hover { background-color: #eeeeee; } .calendar-cell.selected { background-color: #0066cc; color: #ffffff; - border-radius: 4px; + border-radius: 6px; } .calendar-cell.innerday { @@ -58,14 +58,8 @@ } .calendar-header { - background-color: #0066cc; - color: #ffffff; height: 32px; - cursor: pointer; -} - -.calendar-header .calendar-cell:hover { - background-color: #0033cc; + cursor: default; } .calendar-body { @@ -73,3 +67,46 @@ flex-direction: column; cursor: pointer; } + +.calendar-toolbar { + display: flex; + justify-content: space-between; + font-size: 12px; + user-select: none; + font-weight: bold; +} + +.calendar-toolbar a { + color: #000000; + text-decoration: none; + cursor: pointer; +} + +.calendar-toolbar a:hover { + color: #0066cc; +} + +.calendar-toolbar-button { + display: block; + width: 28px; + height: 28px; + line-height: 28px; + text-align: center; + border: 1px soild #000000; + flex-shrink: 0; + flex-grow: 0; +} + +.calendar-toolbar-button.title { + flex-grow: 1; + flex-shrink: 1; +} + +.calendar-toolbar-button.disabled { + color: #aaaaaa; + cursor: not-allowed; +} + +.calendar-toolbar-button.disabled:hover { + color: #aaaaaa; +} diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 38dde082..9d4e0d66 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,12 +1,67 @@ import { defineComponent } from 'vue' -import { getMonthWeeklyDays, WEEK_DAYS } from './utils' +import { getMonthWeeklyDays, WEEK_DAYS, invokeCallback } from './utils' import { TProps, handleDateEnter, cellClassName, trigEvent } from './helper' import './index.css' -const renderPanel = (date: Date, props: TProps) => { +/** + * 比较日期单位 + * @param small 相对早的日期 + * @param big 相对晚的日期 + * @param mode 比较单位 + * @param min 不能小于这个值 + * @returns + */ +const compareDate = (small: Date, big: Date, mode: 'year' | 'month', min: number) => { + if(mode === 'year') { + return big.getFullYear() - small.getFullYear() > min + } else { + const bigMonth = big.getFullYear() * 12 + big.getMonth() + const smallMonth = small.getFullYear() * 12 + small.getMonth() + return bigMonth - smallMonth > min + } +} + +const renderToolbar = (date: Date, props: TProps, pos: number) => { + const dis = [false, false, false, false] + if(props.type === 'range') { + if(pos === 1) { + dis[0] = !compareDate(props.current, props.next, 'year', 1) + dis[1] = !compareDate(props.current, props.next, 'month', 1) + } else { + dis[2] = !compareDate(props.current, props.next, 'month', 1) + dis[3] = !compareDate(props.current, props.next, 'year', 1) + } + } + return ( + + ) +} + +const renderPanel = (date: Date, props: TProps, pos: number) => { return (
+ {renderToolbar(date, props, pos)}
    { WEEK_DAYS.map((day, idx) =>
  1. {day}
  2. ) }
@@ -30,7 +85,7 @@ const renderPanel = (date: Date, props: TProps) => { export default defineComponent({ name: 'DDatepickerCalendar', props: { - mode: { type: String }, + type: { type: String }, current: { type: Date }, next: { type: Date }, dateStart: { type: Date }, @@ -41,6 +96,10 @@ export default defineComponent({ onSelectStart: { type: Function }, onSelectEnd: { type: Function }, onSelecting: { type: Function }, + onPreviousYear: { type: Function }, + onPreviousMonth: { type: Function }, + onNextMonth: { type: Function }, + onNextYear: { type: Function }, }, setup(props) { return () => { @@ -53,8 +112,8 @@ export default defineComponent({ } return (
- {renderPanel(current, props as TProps)} - {props.mode === 'range' ? renderPanel(next, props as TProps) : null} + {renderPanel(current, props as TProps, 0)} + {props.type === 'range' ? renderPanel(next, props as TProps, 1) : null}
) } diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 5768e8f0..7377410e 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -35,16 +35,6 @@ const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capt el.removeEventListener(name, cb, capture) } -const getScrollOffset = (el: Element) => { - let x = 0, y = 0 - while(el.parentElement) { - el = el.parentElement - x += el.scrollLeft - y += el.scrollTop - } - return [x, y] -} - const getHostRange = (host: Element): { left: number right: number @@ -70,15 +60,12 @@ export default defineComponent({ const popCont = ref() const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] - const state = reactive<{ + const inputState = reactive<{ showPanel: boolean panelXPos: 'left' | 'right' panelYPos: 'top' | 'bottom' pointX: string pointY: string - dateStart?: Date - dateEnd?: Date - dateHover?: Date }>({ showPanel: false, panelXPos: 'left', @@ -87,6 +74,19 @@ export default defineComponent({ pointY: '0px', }) + const dateState = reactive<{ + type?: 'normal' | 'range' + dateCurrent?: Date + dateNext?: Date + dateStart?: Date + dateEnd?: Date + dateHover?: Date + }>({ + type: 'range', + dateCurrent: new Date(), + dateNext: new Date(2021, 8, 2) + }) + onMounted(() => { const { value: cont } = container if(!cont) { @@ -94,7 +94,7 @@ export default defineComponent({ } const handleAutoClosePanel = factoryAutoClosePanel(cont, () => { - state.showPanel = false + inputState.showPanel = false }) events.push(attachEvent(document, 'click', handleAutoClosePanel, false)) // // 窗口失焦点时隐藏弹窗 @@ -107,56 +107,83 @@ export default defineComponent({ }) const handleActive = (e: Element) => { - if(state.showPanel) { + if(inputState.showPanel) { return } const range = getHostRange(e) if(range.left > range.right) { - state.panelXPos = 'right' - state.pointX = `${range.width}px` + inputState.panelXPos = 'right' + inputState.pointX = `${range.width}px` } else { - state.panelXPos = 'left' - state.pointX = '0px' + inputState.panelXPos = 'left' + inputState.pointX = '0px' } if(range.top > range.bottom) { - state.panelYPos = 'bottom' - state.pointY = '0px' + inputState.panelYPos = 'bottom' + inputState.pointY = '0px' } else { - state.panelYPos = 'top' - state.pointY = `${range.height}px` + inputState.panelYPos = 'top' + inputState.pointY = `${range.height}px` + } + inputState.showPanel = true + } + + const handleSwitch = (index: number, pos: number, date: Date) => { + switch(index) { + case 0: // previous year + const preYear = new Date(date) + preYear.setFullYear(preYear.getFullYear() - 1) + pos === 0 ? (dateState.dateCurrent = preYear) : (dateState.dateNext = preYear) + break + case 1: // previous month + const preMonth = new Date(date) + preMonth.setMonth(preMonth.getMonth() - 1) + pos === 0 ? (dateState.dateCurrent = preMonth) : (dateState.dateNext = preMonth) + break + case 2: // next month + const nextMonth = new Date(date) + nextMonth.setMonth(nextMonth.getMonth() + 1) + pos === 0 ? (dateState.dateCurrent = nextMonth) : (dateState.dateNext = nextMonth) + break + case 3: // next year + const nextYear = new Date(date) + nextYear.setFullYear(nextYear.getFullYear() + 1) + pos === 0 ? (dateState.dateCurrent = nextYear) : (dateState.dateNext = nextYear) + break } - state.showPanel = true } return () => { - return ( -
+
-
+
{ - state.dateEnd = state.dateHover = undefined - state.dateStart = date + dateState.dateEnd = dateState.dateHover = undefined + dateState.dateStart = date }} - onSelected={(date: Date) => state.dateStart = date} - onSelectStart={(date: Date) => state.dateStart = date} - onSelectEnd={(date: Date) => state.dateEnd = date} - onSelecting={(date: Date) => state.dateHover = date} + onSelected={(date: Date) => dateState.dateStart = date} + onSelectStart={(date: Date) => dateState.dateStart = date} + onSelectEnd={(date: Date) => dateState.dateEnd = date} + onSelecting={(date: Date) => dateState.dateHover = date} + onPreviousYear={(date: Date, pos: number) => handleSwitch(0, pos, date)} + onPreviousMonth={(date: Date, pos: number) => handleSwitch(1, pos, date)} + onNextMonth={(date: Date, pos: number) => handleSwitch(2, pos, date)} + onNextYear={(date: Date, pos: number) => handleSwitch(3, pos, date)} />
-- Gitee From 8df3e15ab1ec545d8404d1bc7b9da4d8528efb68 Mon Sep 17 00:00:00 2001 From: Marvin Date: Sun, 8 Aug 2021 21:00:06 +0800 Subject: [PATCH 09/42] =?UTF-8?q?feat:=20calendar=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=8B=86=E8=A7=A3=E5=92=8C=E7=B1=BB=E5=9E=8B=E7=BB=86=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/components/panel/index.css | 62 ++++++++++ .../calendar/components/panel/index.tsx | 68 +++++++++++ .../calendar/components/toobar/index.css | 42 +++++++ .../calendar/components/toobar/index.tsx | 92 ++++++++++++++ .../datepicker/components/calendar/helper.ts | 35 +----- .../datepicker/components/calendar/index.css | 105 ---------------- .../datepicker/components/calendar/index.tsx | 114 +++--------------- devui/datepicker/components/calendar/types.ts | 50 ++++++++ devui/datepicker/components/calendar/utils.ts | 27 ++++- devui/datepicker/datepicker.tsx | 8 +- 10 files changed, 366 insertions(+), 237 deletions(-) create mode 100644 devui/datepicker/components/calendar/components/panel/index.css create mode 100644 devui/datepicker/components/calendar/components/panel/index.tsx create mode 100644 devui/datepicker/components/calendar/components/toobar/index.css create mode 100644 devui/datepicker/components/calendar/components/toobar/index.tsx create mode 100644 devui/datepicker/components/calendar/types.ts diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/calendar/components/panel/index.css new file mode 100644 index 00000000..640f51e6 --- /dev/null +++ b/devui/datepicker/components/calendar/components/panel/index.css @@ -0,0 +1,62 @@ + +.calendar { + border: 1px solid #dddddd; + padding: 5px; + width: 240px; + box-sizing: border-box; +} + +.calendar-row { + display: flex; + flex-direction: row; + justify-content: space-between; + height: 28px; +} + +.calendar-cell { + width: 100%; + text-align: center; + flex-grow: 1; + flex-shrink: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.calendar-body .calendar-cell:hover { + background-color: #eeeeee; +} + +.calendar-cell.selected { + background-color: #0066cc; + color: #ffffff; + border-radius: 6px; +} + +.calendar-cell.innerday { + background-color: #f1f1f1; +} + +.calendar-cell.disabled { + background-color: #ffffff; + color: #bbbbbb; +} + +.calenday-cell-text { + font-weight: normal; + font-style: normal; + font-size: 12px; + text-align: center; +} + +.calendar-header { + height: 32px; + cursor: default; +} + +.calendar-body { + display: flex; + flex-direction: column; + cursor: pointer; +} diff --git a/devui/datepicker/components/calendar/components/panel/index.tsx b/devui/datepicker/components/calendar/components/panel/index.tsx new file mode 100644 index 00000000..187420b8 --- /dev/null +++ b/devui/datepicker/components/calendar/components/panel/index.tsx @@ -0,0 +1,68 @@ +import { defineComponent } from 'vue' +import { TDatePanelProps } from '../../types' +import { getMonthWeeklyDays, WEEK_DAYS } from '../../utils' +import { handleDateEnter, cellClassName, trigEvent } from '../../helper' +import CalendaToolbar, { CalendaToolbarProps } from '../toobar' +import './index.css' + +export const CalendaDatePanelEventProps = { + onSelected: { type: Function }, + onReset: { type: Function }, + onSelectStart: { type: Function }, + onSelectEnd: { type: Function }, + onSelecting: { type: Function }, +} + +export const CalendaDatePanelProps = { + ...CalendaToolbarProps, + dateStart: { type: Date }, + dateHover: { type: Date }, + dateEnd: { type: Date }, + ...CalendaDatePanelEventProps, +} + +const CalendaDatePanel = defineComponent({ + name: 'CalendaDatePanel', + props: { + pos: { type: Number }, + compare: { type: Date }, + ...CalendaDatePanelProps + }, + setup(_props) { + const props = _props as TDatePanelProps + return () => { + return ( +
+ +
    { + WEEK_DAYS.map(day =>
  1. {day}
  2. ) + }
+
    { + getMonthWeeklyDays(props.current).map((row, idx0) =>
  • { + row.map((day, idx1) => { + return ( + trigEvent(props as TDatePanelProps, day)} + onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} + >{day.date.getDate()} + ) + }) + }
  • ) + }
+
+ ) + } + } +}) + +export default CalendaDatePanel \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toobar/index.css b/devui/datepicker/components/calendar/components/toobar/index.css new file mode 100644 index 00000000..2dd2ffc9 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toobar/index.css @@ -0,0 +1,42 @@ +.calendar-toolbar { + display: flex; + justify-content: space-between; + font-size: 12px; + user-select: none; + font-weight: bold; +} + +.calendar-toolbar a { + color: #000000; + text-decoration: none; + cursor: pointer; +} + +.calendar-toolbar a:hover { + color: #0066cc; +} + +.calendar-toolbar-button { + display: block; + width: 28px; + height: 28px; + line-height: 28px; + text-align: center; + border: 1px soild #000000; + flex-shrink: 0; + flex-grow: 0; +} + +.calendar-toolbar-button.title { + flex-grow: 1; + flex-shrink: 1; +} + +.calendar-toolbar-button.disabled { + color: #aaaaaa; + cursor: not-allowed; +} + +.calendar-toolbar-button.disabled:hover { + color: #aaaaaa; +} diff --git a/devui/datepicker/components/calendar/components/toobar/index.tsx b/devui/datepicker/components/calendar/components/toobar/index.tsx new file mode 100644 index 00000000..c39bdeaa --- /dev/null +++ b/devui/datepicker/components/calendar/components/toobar/index.tsx @@ -0,0 +1,92 @@ +import { defineComponent } from 'vue' +import { invokeCallback, compareDate } from '../../utils' +import './index.css' + +const CalendaToolbarItem = defineComponent({ + name: 'CalendaToolbarItem', + props: { + disabled: { type: Boolean }, + text: { type: String }, + cb: { type: Function }, + pos: { type: Number }, + date: { type: Date } + }, + setup(props) { + return () => { + return ( + invokeCallback(props.cb, props.date, props.pos) + } + >{props.text} + ) + } + } +}) + +export const CalendaToolbarEventProps = { + onPreviousYear: { type: Function }, + onPreviousMonth: { type: Function }, + onNextMonth: { type: Function }, + onNextYear: { type: Function }, +} + +export const CalendaToolbarDataProps = { + type: { type: String }, + mode: { type: String }, + current: { type: Date }, +} + +export const CalendaToolbarProps = { + ...CalendaToolbarDataProps, + ...CalendaToolbarEventProps, +} + +const CalendaToolbar = defineComponent({ + name: 'CalendaToolbar', + props: { + pos: { type: Number }, + compare: { type: Date }, + ...CalendaToolbarProps + }, + setup(props) { + const { + onPreviousYear, + onPreviousMonth, + onNextMonth, + onNextYear, + } = props || {} + return () => { + const { type, current, compare, pos } = props || {} + if (!current) { + return null + } + const dis = [false, false, false, false] + if (type === 'range') { + if (pos === 1) { + dis[0] = !compareDate(compare, current, 'year', 1) + dis[1] = !compareDate(compare, current, 'month', 1) + } else { + dis[2] = !compareDate(current, compare, 'month', 1) + dis[3] = !compareDate(current, compare, 'year', 1) + } + } + return ( +
+ + + { + `${current.getFullYear()}年${(current.getMonth() + 1 + '').padStart(2, '0')}月` + } + '} cb={onNextMonth} /> + >'} cb={onNextYear} /> +
+ ) + } + } +}) + +export default CalendaToolbar \ No newline at end of file diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts index afed1baf..7119b5c4 100644 --- a/devui/datepicker/components/calendar/helper.ts +++ b/devui/datepicker/components/calendar/helper.ts @@ -1,34 +1,11 @@ -import { invokeCallback, TDateCell } from './utils' +import { invokeCallback } from './utils' +import { TDateCell, TDatePanelDataProps, TDatePanelProps } from './types' -export type TEventCallback = (date: Date, position?: number) => void - -export type TProps = ({ - type: 'select' -} | { - type: 'range' - next: Date -}) & { - current: Date - mode: 'month' | 'year' - dateStart?: Date - dateHover?: Date - dateEnd?: Date - onSelected?: TEventCallback - onReset?: TEventCallback - onSelectStart?: TEventCallback - onSelectEnd?: TEventCallback - onSelecting?: TEventCallback - onPreviousYear?: TEventCallback - onPreviousMonth?: TEventCallback - onNextMonth?: TEventCallback - onNextYear?: TEventCallback -} - -export const getDateKey = (date: Date) => { +export const getDateKey = (date: Date): string => { return date.toDateString() } -export const cellClassName = (props: TProps, day: TDateCell) => { +export const cellClassName = (props: TDatePanelDataProps, day: TDateCell): string => { if(day.current !== 0) { return 'calendar-cell disabled' } @@ -55,7 +32,7 @@ export const cellClassName = (props: TProps, day: TDateCell) => { } } -export const trigEvent = (props: TProps, day: TDateCell) => { +export const trigEvent = (props: TDatePanelProps, day: TDateCell): void => { if(day.current !== 0) { return } @@ -72,7 +49,7 @@ export const trigEvent = (props: TProps, day: TDateCell) => { } } -export const handleDateEnter = (props: TProps, day: TDateCell) => { +export const handleDateEnter = (props: TDatePanelProps, day: TDateCell): void => { if(day.current !== 0) { return } diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css index b3538df2..34eb5952 100644 --- a/devui/datepicker/components/calendar/index.css +++ b/devui/datepicker/components/calendar/index.css @@ -5,108 +5,3 @@ justify-content: flex-start; align-items: flex-start; } - -.calendar { - border: 1px solid #dddddd; - padding: 5px; - width: 240px; - box-sizing: border-box; -} - -.calendar-row { - display: flex; - flex-direction: row; - justify-content: space-between; - height: 28px; -} - -.calendar-cell { - width: 100%; - text-align: center; - flex-grow: 1; - flex-shrink: 1; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} - -.calendar-body .calendar-cell:hover { - background-color: #eeeeee; -} - -.calendar-cell.selected { - background-color: #0066cc; - color: #ffffff; - border-radius: 6px; -} - -.calendar-cell.innerday { - background-color: #f1f1f1; -} - -.calendar-cell.disabled { - background-color: #ffffff; - color: #bbbbbb; -} - -.calenday-cell-text { - font-weight: normal; - font-style: normal; - font-size: 12px; - text-align: center; -} - -.calendar-header { - height: 32px; - cursor: default; -} - -.calendar-body { - display: flex; - flex-direction: column; - cursor: pointer; -} - -.calendar-toolbar { - display: flex; - justify-content: space-between; - font-size: 12px; - user-select: none; - font-weight: bold; -} - -.calendar-toolbar a { - color: #000000; - text-decoration: none; - cursor: pointer; -} - -.calendar-toolbar a:hover { - color: #0066cc; -} - -.calendar-toolbar-button { - display: block; - width: 28px; - height: 28px; - line-height: 28px; - text-align: center; - border: 1px soild #000000; - flex-shrink: 0; - flex-grow: 0; -} - -.calendar-toolbar-button.title { - flex-grow: 1; - flex-shrink: 1; -} - -.calendar-toolbar-button.disabled { - color: #aaaaaa; - cursor: not-allowed; -} - -.calendar-toolbar-button.disabled:hover { - color: #aaaaaa; -} diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 9d4e0d66..707aa069 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,105 +1,17 @@ import { defineComponent } from 'vue' -import { getMonthWeeklyDays, WEEK_DAYS, invokeCallback } from './utils' -import { TProps, handleDateEnter, cellClassName, trigEvent } from './helper' +import { getMonthWeeklyDays, WEEK_DAYS, invokeCallback, compareDate } from './utils' +import { handleDateEnter, cellClassName, trigEvent } from './helper' +import CalendaToolbar, { CalendaToolbarProps } from './components/toobar' +import { TProps, TDatePanelProps } from './types' +import CalendaDatePanel, { CalendaDatePanelProps } from './components/panel' import './index.css' -/** - * 比较日期单位 - * @param small 相对早的日期 - * @param big 相对晚的日期 - * @param mode 比较单位 - * @param min 不能小于这个值 - * @returns - */ -const compareDate = (small: Date, big: Date, mode: 'year' | 'month', min: number) => { - if(mode === 'year') { - return big.getFullYear() - small.getFullYear() > min - } else { - const bigMonth = big.getFullYear() * 12 + big.getMonth() - const smallMonth = small.getFullYear() * 12 + small.getMonth() - return bigMonth - smallMonth > min - } -} - -const renderToolbar = (date: Date, props: TProps, pos: number) => { - const dis = [false, false, false, false] - if(props.type === 'range') { - if(pos === 1) { - dis[0] = !compareDate(props.current, props.next, 'year', 1) - dis[1] = !compareDate(props.current, props.next, 'month', 1) - } else { - dis[2] = !compareDate(props.current, props.next, 'month', 1) - dis[3] = !compareDate(props.current, props.next, 'year', 1) - } - } - return ( - - ) -} - -const renderPanel = (date: Date, props: TProps, pos: number) => { - return ( -
- {renderToolbar(date, props, pos)} -
    { - WEEK_DAYS.map((day, idx) =>
  1. {day}
  2. ) - }
-
    { - getMonthWeeklyDays(date).map((row, idx0) =>
  • { - row.map((day, idx1) => { - return ( - trigEvent(props, day)} - onMouseenter={() => handleDateEnter(props, day)} - >{day.date.getDate()} - ) - }) - }
  • ) - }
-
- ) -} - export default defineComponent({ name: 'DDatepickerCalendar', props: { - type: { type: String }, - current: { type: Date }, next: { type: Date }, - dateStart: { type: Date }, - dateHover: { type: Date }, - dateEnd: { type: Date }, - onSelected: { type: Function }, - onReset: { type: Function }, - onSelectStart: { type: Function }, - onSelectEnd: { type: Function }, - onSelecting: { type: Function }, - onPreviousYear: { type: Function }, - onPreviousMonth: { type: Function }, - onNextMonth: { type: Function }, - onNextYear: { type: Function }, + ...CalendaDatePanelProps }, setup(props) { return () => { @@ -110,10 +22,20 @@ export default defineComponent({ if(!(next instanceof Date)) { next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } + const P = props as TProps return (
- {renderPanel(current, props as TProps, 0)} - {props.type === 'range' ? renderPanel(next, props as TProps, 1) : null} + + { + props.type === 'range' + ? + : null + }
) } diff --git a/devui/datepicker/components/calendar/types.ts b/devui/datepicker/components/calendar/types.ts new file mode 100644 index 00000000..8294c0eb --- /dev/null +++ b/devui/datepicker/components/calendar/types.ts @@ -0,0 +1,50 @@ +export type TDateCell = { date: Date; current: -1 | 0 | 1; } +export type TDatePanelMode = 'month' | 'year' +export type TDatePanelType = 'select' | 'range' +export type TEventCallback = (date: Date, position?: number) => void + +export type TDateConfig = { + type?: TDatePanelType + mode?: TDatePanelMode + current?: Date +} + +export type TDateSelectingBase = { + dateStart?: Date + dateEnd?: Date + dateHover?: Date +} + +export type TDateToolbarEventProps = { + onPreviousYear?: TEventCallback + onPreviousMonth?: TEventCallback + onNextMonth?: TEventCallback + onNextYear?: TEventCallback +} + +export type TDateToolbarDataProps = TDateConfig & { + pos?: number + compare?: Date +} + +export type TDateToolbarProps = TDateToolbarDataProps & TDateToolbarEventProps + +export type TDatePanelEventProps = TDateToolbarEventProps & { + onSelected?: TEventCallback + onReset?: TEventCallback + onSelectStart?: TEventCallback + onSelectEnd?: TEventCallback + onSelecting?: TEventCallback +} + +export type TDatePanelDataProps = TDateToolbarDataProps & TDateSelectingBase + +export type TDatePanelProps = TDatePanelDataProps & TDatePanelEventProps + + +export type TProps = ({ + type: 'select' +} | { + type: 'range' + next: Date +}) & TDateConfig & TDateSelectingBase & TDatePanelEventProps \ No newline at end of file diff --git a/devui/datepicker/components/calendar/utils.ts b/devui/datepicker/components/calendar/utils.ts index 6410d095..9971b446 100644 --- a/devui/datepicker/components/calendar/utils.ts +++ b/devui/datepicker/components/calendar/utils.ts @@ -1,3 +1,5 @@ +import { TDateCell } from './types' + const getHumanDate = (d: Date) => { const year = d.getFullYear() const month = d.getMonth() + 1 @@ -17,8 +19,6 @@ const getHumanDate = (d: Date) => { } } -export type TDateCell = { date: Date; current: -1 | 0 | 1; } - const getMonthDays = (year: number, month: number) => { const first = new Date(year, month - 1, 1) const last = new Date(year, month, 0) @@ -45,7 +45,7 @@ const getMonthDays = (year: number, month: number) => { } export const getMonthWeeklyDays = (date: any = new Date()) => { - if(!(date instanceof Date)) { + if (!(date instanceof Date)) { date = new Date() } const { year, month } = getHumanDate(date) @@ -61,4 +61,25 @@ export const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'] export const invokeCallback = (cb: any, ...args: any[]) => { typeof cb === 'function' && cb(...args) +} + +export /** +* 比较日期单位 +* @param small 相对早的日期 +* @param big 相对晚的日期 +* @param mode 比较单位 +* @param min 不能小于这个值 +* @returns +*/ +const compareDate = (small: Date | undefined, big: Date | undefined, mode: 'year' | 'month', min: number) => { + if (!small || !big) { + return true + } + if (mode === 'year') { + return big.getFullYear() - small.getFullYear() > min + } else { + const bigMonth = big.getFullYear() * 12 + big.getMonth() + const smallMonth = small.getFullYear() * 12 + small.getMonth() + return bigMonth - smallMonth > min + } } \ No newline at end of file diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 7377410e..a8bc9b19 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,7 +1,7 @@ import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' -import Input from './components/input/index' -import PopPanel from './components/pop-panel/index' -import Calendar from './components/calendar/index' +import Input from './components/input' +import PopPanel from './components/pop-panel' +import Calendar from './components/calendar' import './datepicker.css' @@ -75,7 +75,7 @@ export default defineComponent({ }) const dateState = reactive<{ - type?: 'normal' | 'range' + type?: 'select' | 'range' dateCurrent?: Date dateNext?: Date dateStart?: Date -- Gitee From 1b1091487d4578192f2a9960a9d00189d072adb7 Mon Sep 17 00:00:00 2001 From: Marvin Date: Mon, 9 Aug 2021 08:07:21 +0800 Subject: [PATCH 10/42] feat: code clean up --- devui/datepicker/components/calendar/index.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 707aa069..eea1c4fc 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,8 +1,4 @@ import { defineComponent } from 'vue' -import { getMonthWeeklyDays, WEEK_DAYS, invokeCallback, compareDate } from './utils' -import { handleDateEnter, cellClassName, trigEvent } from './helper' -import CalendaToolbar, { CalendaToolbarProps } from './components/toobar' -import { TProps, TDatePanelProps } from './types' import CalendaDatePanel, { CalendaDatePanelProps } from './components/panel' import './index.css' @@ -22,18 +18,12 @@ export default defineComponent({ if(!(next instanceof Date)) { next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } - const P = props as TProps return (
- + { props.type === 'range' - ? + ? : null }
-- Gitee From 8b5c41087d6248aab63323bcfd42fcf8a2c9f2c5 Mon Sep 17 00:00:00 2001 From: Marvin Date: Mon, 9 Aug 2021 08:39:04 +0800 Subject: [PATCH 11/42] =?UTF-8?q?feat:=20=E5=90=88=E5=B9=B6fork=E6=BA=90?= =?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E7=BB=84=E4=BB=B6=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/components/panel/index.css | 12 ++++++++++++ devui/datepicker/components/pop-panel/index.css | 2 ++ devui/datepicker/index.ts | 10 ++++++++++ devui/vue-devui.ts | 5 +++-- sites/.vitepress/config/sidebar.ts | 1 + sites/components/datepicker/index.md | 17 +++++++++++++++++ 6 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 devui/datepicker/index.ts create mode 100644 sites/components/datepicker/index.md diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/calendar/components/panel/index.css index 640f51e6..98f3b631 100644 --- a/devui/datepicker/components/calendar/components/panel/index.css +++ b/devui/datepicker/components/calendar/components/panel/index.css @@ -26,6 +26,7 @@ .calendar-body .calendar-cell:hover { background-color: #eeeeee; + color: #000000; } .calendar-cell.selected { @@ -34,6 +35,11 @@ border-radius: 6px; } +.calendar-cell.selected:hover { + background-color: #0066cc; + color: #ffffff; +} + .calendar-cell.innerday { background-color: #f1f1f1; } @@ -53,10 +59,16 @@ .calendar-header { height: 32px; cursor: default; + padding: 0; + margin: 0; + list-style: none; } .calendar-body { display: flex; flex-direction: column; cursor: pointer; + padding: 0; + margin: 0; + list-style: none; } diff --git a/devui/datepicker/components/pop-panel/index.css b/devui/datepicker/components/pop-panel/index.css index 78b7d64e..ba794c3e 100644 --- a/devui/datepicker/components/pop-panel/index.css +++ b/devui/datepicker/components/pop-panel/index.css @@ -1,4 +1,6 @@ .datapicker-pop-panel { display: block; position: absolute; + background-color: #ffffff; + z-index: 99; } diff --git a/devui/datepicker/index.ts b/devui/datepicker/index.ts new file mode 100644 index 00000000..17134f3e --- /dev/null +++ b/devui/datepicker/index.ts @@ -0,0 +1,10 @@ +import { App } from 'vue' +import DatePicker from './datepicker' + +DatePicker.install = function(Vue: App) { + Vue.component(DatePicker.name, DatePicker) +}; + +DatePicker.version = '0.0.1' + +export default DatePicker diff --git a/devui/vue-devui.ts b/devui/vue-devui.ts index 009113da..6bb06250 100644 --- a/devui/vue-devui.ts +++ b/devui/vue-devui.ts @@ -13,6 +13,7 @@ import Alert from './alert'; // 数据录入 import Checkbox from './checkbox'; +import DatePicker from './datepicker'; import Radio from './radio'; import Switch from './switch'; import TagsInput from './tags-input'; @@ -23,7 +24,7 @@ import Avatar from './avatar'; import Carousel from './carousel'; function install(app: App): void { - const packages = [ Button, Icon, Panel, Tabs, Alert, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel ]; + const packages = [ Button, Icon, Panel, Tabs, Alert, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel, DatePicker ]; packages.forEach((item: any) => { if (item.install) { app.use(item); @@ -33,5 +34,5 @@ function install(app: App): void { }); } -export { Button, Icon, Panel, Tabs, Alert, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel }; +export { Button, Icon, Panel, Tabs, Alert, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel, DatePicker }; export default { install, version: '0.0.1' }; \ No newline at end of file diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index 9866743e..c6c5d9c9 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -25,6 +25,7 @@ const sidebar = { text: '数据录入', children: [ { text: 'Checkbox 复选框', link: '/components/checkbox/' }, + { text: 'DatePicker 日期选择器', link: '/components/datepicker/' }, { text: 'Radio 单选框', link: '/components/radio/' }, { text: 'Switch 开关', link: '/components/switch/' }, { text: 'TagsInput 标签输入', link: '/components/tags-input/' }, diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md new file mode 100644 index 00000000..d13aa97f --- /dev/null +++ b/sites/components/datepicker/index.md @@ -0,0 +1,17 @@ +# DatePicker 日期选择器 + +日期、时间的可视化输入。 + +### 何时使用 + +- 可视化操作日期选择 +- 选择日期区间 +- 限制日期输入 + +### 简单使用 + + + +```jsx + +``` -- Gitee From c9ae609d262944bbd2a91c4f9ed8898cc56a40d0 Mon Sep 17 00:00:00 2001 From: Marvin Date: Mon, 9 Aug 2021 08:44:06 +0800 Subject: [PATCH 12/42] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0icon=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 45d6914c..efa0c3bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1202,8 +1202,8 @@ "@devui-design/icons@^1.3.0": version "1.3.0" - resolved "https://registry.nlark.com/@devui-design/icons/download/@devui-design/icons-1.3.0.tgz#5a3006a31ee4f62e3f9837b68c031898ff148b88" - integrity sha1-WjAGox7k9i4/mDe2jAMYmP8Ui4g= + resolved "https://registry.npmjs.org/@devui-design/icons/-/icons-1.3.0.tgz#5a3006a31ee4f62e3f9837b68c031898ff148b88" + integrity sha512-eg9PcDXYn1BaUCo6qP7dszFJ4uRycxASLdKzpz27AW34HbHx4kFce36Fhfr2niRjGKYllcMgYe7hgP5KYVOZ8Q== "@docsearch/css@^1.0.0-alpha.28": version "1.0.0-alpha.28" -- Gitee From aebf7ddde04b869e38a4abd419981baaf4bdfcdd Mon Sep 17 00:00:00 2001 From: mrundef Date: Mon, 9 Aug 2021 11:18:01 +0800 Subject: [PATCH 13/42] =?UTF-8?q?feat:=20=E6=9B=B4=E6=8D=A2icon=E5=92=8C?= =?UTF-8?q?=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/components/panel/index.css | 23 ++++++------- .../calendar/components/panel/index.tsx | 12 +++---- .../calendar/components/toobar/icons.tsx | 33 +++++++++++++++++++ .../calendar/components/toobar/index.tsx | 22 ++++++++----- .../datepicker/components/calendar/helper.ts | 14 ++++---- devui/datepicker/components/icon/index.css | 9 ----- devui/datepicker/components/icon/index.tsx | 17 ---------- devui/datepicker/components/input/index.tsx | 4 +-- 8 files changed, 74 insertions(+), 60 deletions(-) create mode 100644 devui/datepicker/components/calendar/components/toobar/icons.tsx delete mode 100644 devui/datepicker/components/icon/index.css delete mode 100644 devui/datepicker/components/icon/index.tsx diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/calendar/components/panel/index.css index 98f3b631..67254d45 100644 --- a/devui/datepicker/components/calendar/components/panel/index.css +++ b/devui/datepicker/components/calendar/components/panel/index.css @@ -1,19 +1,19 @@ -.calendar { +.calendar-panel { border: 1px solid #dddddd; padding: 5px; width: 240px; box-sizing: border-box; } -.calendar-row { +.calendar-panel-row { display: flex; flex-direction: row; justify-content: space-between; height: 28px; } -.calendar-cell { +.calendar-panel-cell { width: 100%; text-align: center; flex-grow: 1; @@ -24,39 +24,40 @@ justify-content: center; } -.calendar-body .calendar-cell:hover { +.calendar-panel-body .calendar-panel-cell:hover { background-color: #eeeeee; color: #000000; } -.calendar-cell.selected { +.calendar-panel-cell.selected { background-color: #0066cc; color: #ffffff; border-radius: 6px; } -.calendar-cell.selected:hover { +.calendar-panel-cell.selected:hover { background-color: #0066cc; color: #ffffff; } -.calendar-cell.innerday { +.calendar-panel-cell.innerday { background-color: #f1f1f1; } -.calendar-cell.disabled { +.calendar-panel-cell.disabled { background-color: #ffffff; color: #bbbbbb; } -.calenday-cell-text { +.calendar-head-cell-text, +.calendar-panel-cell-text { font-weight: normal; font-style: normal; font-size: 12px; text-align: center; } -.calendar-header { +.calendar-panel-header { height: 32px; cursor: default; padding: 0; @@ -64,7 +65,7 @@ list-style: none; } -.calendar-body { +.calendar-panel-body { display: flex; flex-direction: column; cursor: pointer; diff --git a/devui/datepicker/components/calendar/components/panel/index.tsx b/devui/datepicker/components/calendar/components/panel/index.tsx index 187420b8..0cb6f9e9 100644 --- a/devui/datepicker/components/calendar/components/panel/index.tsx +++ b/devui/datepicker/components/calendar/components/panel/index.tsx @@ -32,7 +32,7 @@ const CalendaDatePanel = defineComponent({ const props = _props as TDatePanelProps return () => { return ( -
+
-
    { - WEEK_DAYS.map(day =>
  1. {day}
  2. ) +
      { + WEEK_DAYS.map(day =>
    1. {day}
    2. ) }
    -
      { - getMonthWeeklyDays(props.current).map((row, idx0) =>
    • { +
        { + getMonthWeeklyDays(props.current).map((row, idx0) =>
      • { row.map((day, idx1) => { return ( trigEvent(props as TDatePanelProps, day)} onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} - >{day.date.getDate()} + >{day.date.getDate()} ) }) }
      • ) diff --git a/devui/datepicker/components/calendar/components/toobar/icons.tsx b/devui/datepicker/components/calendar/components/toobar/icons.tsx new file mode 100644 index 00000000..189ec073 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toobar/icons.tsx @@ -0,0 +1,33 @@ +import { defineComponent } from 'vue' + +export const Year = defineComponent({ + props: { + color: String, + rotate: Number, + }, + setup(props) { + return () => ( + + + + + + ) + } +}) + +export const Month = defineComponent({ + props: { + color: String, + rotate: Number, + }, + setup(props) { + return () => ( + + + + + + ) + } +}) \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toobar/index.tsx b/devui/datepicker/components/calendar/components/toobar/index.tsx index c39bdeaa..95079ea2 100644 --- a/devui/datepicker/components/calendar/components/toobar/index.tsx +++ b/devui/datepicker/components/calendar/components/toobar/index.tsx @@ -1,5 +1,6 @@ -import { defineComponent } from 'vue' +import { defineComponent, DefineComponent } from 'vue' import { invokeCallback, compareDate } from '../../utils' +import { Year, Month } from './icons' import './index.css' const CalendaToolbarItem = defineComponent({ @@ -7,21 +8,26 @@ const CalendaToolbarItem = defineComponent({ props: { disabled: { type: Boolean }, text: { type: String }, + rotate: { type: Number }, cb: { type: Function }, pos: { type: Number }, - date: { type: Date } + date: { type: Date }, + button: { type: Object }, }, setup(props) { return () => { + const Btn = (props.button || Year) as DefineComponent<{ color: StringConstructor; rotate: NumberConstructor; }> + const color = props.disabled ? '#cfd0d3' : '#585d6b' + const rotate = props.rotate || 0 return ( invokeCallback(props.cb, props.date, props.pos) } - >{props.text} + > ) } } @@ -76,13 +82,13 @@ const CalendaToolbar = defineComponent({ } return (
        - - + + { `${current.getFullYear()}年${(current.getMonth() + 1 + '').padStart(2, '0')}月` } - '} cb={onNextMonth} /> - >'} cb={onNextYear} /> + +
        ) } diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts index 7119b5c4..3ce47850 100644 --- a/devui/datepicker/components/calendar/helper.ts +++ b/devui/datepicker/components/calendar/helper.ts @@ -5,30 +5,30 @@ export const getDateKey = (date: Date): string => { return date.toDateString() } -export const cellClassName = (props: TDatePanelDataProps, day: TDateCell): string => { +export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = 'calendar-panel-cell'): string => { if(day.current !== 0) { - return 'calendar-cell disabled' + return `${base} disabled` } const key = getDateKey(day.date) if (props.type === 'range') { if (props.dateStart) { if (getDateKey(props.dateStart) === key) { - return `calendar-cell selected` + return `${base} selected` } if (props.dateEnd && getDateKey(props.dateEnd) === key) { - return `calendar-cell selected` + return `${base} selected` } const innerEnd = props.dateEnd || props.dateHover if (innerEnd) { const range = innerEnd > props.dateStart ? [props.dateStart, innerEnd] : [innerEnd, props.dateStart] if (day.date > range[0] && day.date < range[1]) { - return `calendar-cell innerday` + return `${base} innerday` } } } - return `calendar-cell` + return base } else { - return props.dateStart && getDateKey(props.dateStart) === key ? `calendar-cell selected` : `calendar-cell` + return props.dateStart && getDateKey(props.dateStart) === key ? `${base} selected` : base } } diff --git a/devui/datepicker/components/icon/index.css b/devui/datepicker/components/icon/index.css deleted file mode 100644 index 51380477..00000000 --- a/devui/datepicker/components/icon/index.css +++ /dev/null @@ -1,9 +0,0 @@ -.datepicker-input-icon { - width: 16px; - height: 16px; - box-sizing: border-box; - border-radius: 0; - flex-shrink: 0; - flex-grow: 0; - background-color: #cccccc; -} diff --git a/devui/datepicker/components/icon/index.tsx b/devui/datepicker/components/icon/index.tsx deleted file mode 100644 index 938b91ad..00000000 --- a/devui/datepicker/components/icon/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { defineComponent } from 'vue' -import './index.css' - -export default defineComponent({ - name: 'DDatepickerInputIcon', - props: { - marginLeft: { type: Number, default: 0 }, - marginRight: { type: Number, default: 0 }, - }, - setup(props, ctx) { - const { marginLeft = 0, marginRight = 0 } = props || {} - return () => - } -}) \ No newline at end of file diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx index 0539dc33..efcdcabb 100644 --- a/devui/datepicker/components/input/index.tsx +++ b/devui/datepicker/components/input/index.tsx @@ -1,5 +1,5 @@ import { defineComponent, ref } from 'vue' -import DataPickerInputIcon from '../icon/index' +import Icon from '../../../icon' import './index.css' @@ -28,7 +28,7 @@ export default defineComponent({ onClick={handleClick} > - +
) } -- Gitee From 3b280fe142e4a028e11e4c5017df4fc875d2c099 Mon Sep 17 00:00:00 2001 From: mrundef Date: Mon, 9 Aug 2021 12:51:00 +0800 Subject: [PATCH 14/42] =?UTF-8?q?feat:=20=E5=87=BD=E6=95=B0=E5=BC=8F?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/components/panel/index.tsx | 94 +++++++----------- .../calendar/components/toobar/icons.tsx | 33 ------- .../calendar/components/toobar/index.tsx | 98 ------------------- .../calendar/components/toolbar/icons.tsx | 27 +++++ .../calendar/components/toolbar/index.css | 17 ++++ .../calendar/components/toolbar/index.tsx | 38 +++++++ .../index.css => toolbar/toolbar-item.css} | 18 ---- .../components/toolbar/toolbar-item.tsx | 42 ++++++++ .../datepicker/components/calendar/index.tsx | 54 +++++----- devui/datepicker/components/calendar/types.ts | 2 +- devui/datepicker/components/calendar/utils.ts | 2 +- 11 files changed, 185 insertions(+), 240 deletions(-) delete mode 100644 devui/datepicker/components/calendar/components/toobar/icons.tsx delete mode 100644 devui/datepicker/components/calendar/components/toobar/index.tsx create mode 100644 devui/datepicker/components/calendar/components/toolbar/icons.tsx create mode 100644 devui/datepicker/components/calendar/components/toolbar/index.css create mode 100644 devui/datepicker/components/calendar/components/toolbar/index.tsx rename devui/datepicker/components/calendar/components/{toobar/index.css => toolbar/toolbar-item.css} (59%) create mode 100644 devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx diff --git a/devui/datepicker/components/calendar/components/panel/index.tsx b/devui/datepicker/components/calendar/components/panel/index.tsx index 0cb6f9e9..84892677 100644 --- a/devui/datepicker/components/calendar/components/panel/index.tsx +++ b/devui/datepicker/components/calendar/components/panel/index.tsx @@ -1,68 +1,40 @@ -import { defineComponent } from 'vue' import { TDatePanelProps } from '../../types' import { getMonthWeeklyDays, WEEK_DAYS } from '../../utils' import { handleDateEnter, cellClassName, trigEvent } from '../../helper' -import CalendaToolbar, { CalendaToolbarProps } from '../toobar' +import Toolbar from '../toolbar' import './index.css' -export const CalendaDatePanelEventProps = { - onSelected: { type: Function }, - onReset: { type: Function }, - onSelectStart: { type: Function }, - onSelectEnd: { type: Function }, - onSelecting: { type: Function }, +const CalendarDatePanel = (props: TDatePanelProps) => { + return ( +
+ +
    { + WEEK_DAYS.map(day =>
  1. {day}
  2. ) + }
+
    { + getMonthWeeklyDays(props.current).map(row =>
  • { + row.map(day => { + return ( + trigEvent(props as TDatePanelProps, day)} + onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} + >{day.date.getDate()} + ) + }) + }
  • ) + }
+
+ ) } -export const CalendaDatePanelProps = { - ...CalendaToolbarProps, - dateStart: { type: Date }, - dateHover: { type: Date }, - dateEnd: { type: Date }, - ...CalendaDatePanelEventProps, -} - -const CalendaDatePanel = defineComponent({ - name: 'CalendaDatePanel', - props: { - pos: { type: Number }, - compare: { type: Date }, - ...CalendaDatePanelProps - }, - setup(_props) { - const props = _props as TDatePanelProps - return () => { - return ( -
- -
    { - WEEK_DAYS.map(day =>
  1. {day}
  2. ) - }
-
    { - getMonthWeeklyDays(props.current).map((row, idx0) =>
  • { - row.map((day, idx1) => { - return ( - trigEvent(props as TDatePanelProps, day)} - onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} - >{day.date.getDate()} - ) - }) - }
  • ) - }
-
- ) - } - } -}) - -export default CalendaDatePanel \ No newline at end of file +export default CalendarDatePanel \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toobar/icons.tsx b/devui/datepicker/components/calendar/components/toobar/icons.tsx deleted file mode 100644 index 189ec073..00000000 --- a/devui/datepicker/components/calendar/components/toobar/icons.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { defineComponent } from 'vue' - -export const Year = defineComponent({ - props: { - color: String, - rotate: Number, - }, - setup(props) { - return () => ( - - - - - - ) - } -}) - -export const Month = defineComponent({ - props: { - color: String, - rotate: Number, - }, - setup(props) { - return () => ( - - - - - - ) - } -}) \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toobar/index.tsx b/devui/datepicker/components/calendar/components/toobar/index.tsx deleted file mode 100644 index 95079ea2..00000000 --- a/devui/datepicker/components/calendar/components/toobar/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { defineComponent, DefineComponent } from 'vue' -import { invokeCallback, compareDate } from '../../utils' -import { Year, Month } from './icons' -import './index.css' - -const CalendaToolbarItem = defineComponent({ - name: 'CalendaToolbarItem', - props: { - disabled: { type: Boolean }, - text: { type: String }, - rotate: { type: Number }, - cb: { type: Function }, - pos: { type: Number }, - date: { type: Date }, - button: { type: Object }, - }, - setup(props) { - return () => { - const Btn = (props.button || Year) as DefineComponent<{ color: StringConstructor; rotate: NumberConstructor; }> - const color = props.disabled ? '#cfd0d3' : '#585d6b' - const rotate = props.rotate || 0 - return ( - invokeCallback(props.cb, props.date, props.pos) - } - > - ) - } - } -}) - -export const CalendaToolbarEventProps = { - onPreviousYear: { type: Function }, - onPreviousMonth: { type: Function }, - onNextMonth: { type: Function }, - onNextYear: { type: Function }, -} - -export const CalendaToolbarDataProps = { - type: { type: String }, - mode: { type: String }, - current: { type: Date }, -} - -export const CalendaToolbarProps = { - ...CalendaToolbarDataProps, - ...CalendaToolbarEventProps, -} - -const CalendaToolbar = defineComponent({ - name: 'CalendaToolbar', - props: { - pos: { type: Number }, - compare: { type: Date }, - ...CalendaToolbarProps - }, - setup(props) { - const { - onPreviousYear, - onPreviousMonth, - onNextMonth, - onNextYear, - } = props || {} - return () => { - const { type, current, compare, pos } = props || {} - if (!current) { - return null - } - const dis = [false, false, false, false] - if (type === 'range') { - if (pos === 1) { - dis[0] = !compareDate(compare, current, 'year', 1) - dis[1] = !compareDate(compare, current, 'month', 1) - } else { - dis[2] = !compareDate(current, compare, 'month', 1) - dis[3] = !compareDate(current, compare, 'year', 1) - } - } - return ( - - ) - } - } -}) - -export default CalendaToolbar \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toolbar/icons.tsx b/devui/datepicker/components/calendar/components/toolbar/icons.tsx new file mode 100644 index 00000000..c6740da1 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/icons.tsx @@ -0,0 +1,27 @@ +export type TIconSvgProps = { + color?: string + rotate?: number +} +export type TIconSvg = (props: TIconSvgProps) => any + +export const Year = (props: TIconSvgProps) => { + const { color = '#585d6b', rotate = 0 } = props + return ( + + + + + + ) +} + +export const Month = (props: TIconSvgProps) => { + const { color = '#585d6b', rotate = 0 } = props + return ( + + + + + + ) +} \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toolbar/index.css b/devui/datepicker/components/calendar/components/toolbar/index.css new file mode 100644 index 00000000..5046bb6c --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/index.css @@ -0,0 +1,17 @@ +.calendar-toolbar { + display: flex; + justify-content: space-between; + font-size: 12px; + user-select: none; + font-weight: bold; +} + +.calendar-toolbar a { + color: #000000; + text-decoration: none; + cursor: pointer; +} + +.calendar-toolbar a:hover { + color: #0066cc; +} diff --git a/devui/datepicker/components/calendar/components/toolbar/index.tsx b/devui/datepicker/components/calendar/components/toolbar/index.tsx new file mode 100644 index 00000000..8e8124e0 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/index.tsx @@ -0,0 +1,38 @@ +import { compareDate } from '../../utils' +import { Year, Month } from './icons' +import { TDateToolbarProps } from '../../types' +import Item, { CalendarToolbarTitle as Title } from './toolbar-item' +import './index.css' + +const CalendarToolbar = (props: TDateToolbarProps) => { + const { + type, current, compare, pos, + onPreviousYear, + onPreviousMonth, + onNextMonth, + onNextYear, + } = props + + const dis = [false, false, false, false] + if (type === 'range') { + if (pos === 1) { + dis[0] = !compareDate(compare, current, 'year', 1) + dis[1] = !compareDate(compare, current, 'month', 1) + } else { + dis[2] = !compareDate(current, compare, 'month', 1) + dis[3] = !compareDate(current, compare, 'year', 1) + } + } + + return ( +
+ + + + <Item disabled={dis[2]} date={current} pos={pos} button={Month} rotate={90} cb={onNextMonth} /> + <Item disabled={dis[3]} date={current} pos={pos} button={Year} rotate={180} cb={onNextYear} /> + </div> + ) +} + +export default CalendarToolbar \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toobar/index.css b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.css similarity index 59% rename from devui/datepicker/components/calendar/components/toobar/index.css rename to devui/datepicker/components/calendar/components/toolbar/toolbar-item.css index 2dd2ffc9..e207dcb3 100644 --- a/devui/datepicker/components/calendar/components/toobar/index.css +++ b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.css @@ -1,21 +1,3 @@ -.calendar-toolbar { - display: flex; - justify-content: space-between; - font-size: 12px; - user-select: none; - font-weight: bold; -} - -.calendar-toolbar a { - color: #000000; - text-decoration: none; - cursor: pointer; -} - -.calendar-toolbar a:hover { - color: #0066cc; -} - .calendar-toolbar-button { display: block; width: 28px; diff --git a/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx new file mode 100644 index 00000000..5aae2be1 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx @@ -0,0 +1,42 @@ +import { invokeCallback } from '../../utils' +import { TIconSvg } from './icons' +import './toolbar-item.css' + +export type TCalendarToolbarItemProps = { + disabled?: boolean + rotate?: number + cb?: (...args: any[]) => void + pos: number + date: Date + button: TIconSvg +} + +const CalendarToolbarItem = (props: TCalendarToolbarItemProps) => { + const { + button: Btn, + disabled = false, + rotate = 0, + date, + pos, + cb, + } = props + const color = disabled ? '#cfd0d3' : '#585d6b' + const className = `calendar-toolbar-button ${disabled ? 'disabled' : ''}` + const handleClick = disabled ? undefined : () => invokeCallback(props.cb, date, pos) + return ( + <a className={className} onClick={handleClick}> + <Btn color={color} rotate={rotate} /> + </a> + ) +} + +export const CalendarToolbarTitle = (props: { date: Date; }) => { + const { date } = props + return ( + <a className="calendar-toolbar-button title">{ + `${date.getFullYear()}年${(date.getMonth() + 1 + '').padStart(2, '0')}月` + }</a> + ) +} + +export default CalendarToolbarItem \ No newline at end of file diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index eea1c4fc..1da65692 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,33 +1,31 @@ -import { defineComponent } from 'vue' -import CalendaDatePanel, { CalendaDatePanelProps } from './components/panel' +import { TProps } from './types' +import CalendarDatePanel from './components/panel' import './index.css' -export default defineComponent({ - name: 'DDatepickerCalendar', - props: { - next: { type: Date }, - ...CalendaDatePanelProps - }, - setup(props) { - return () => { - let { current, next } = props || {} - if (!(current instanceof Date)) { - current = new Date - } - if(!(next instanceof Date)) { - next = new Date(current.getFullYear(), current.getMonth() + 1, 1) - } - return ( - <div class="calendar-container"> - <CalendaDatePanel { ...props } pos={0} current={current} compare={next} /> - { - props.type === 'range' - ? <CalendaDatePanel { ...props } pos={1} current={next} compare={current} /> - : null - } - </div> - ) +const Calendar = (props: TProps) => { + let { current } = props + if (!(current instanceof Date)) { + current = new Date + } + if(props.type === 'range') { + let { next } = props + if(!(next instanceof Date)) { + next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } + return ( + <div className="calendar-container"> + <CalendarDatePanel { ...props } pos={0} current={current} compare={next} /> + <CalendarDatePanel { ...props } pos={1} current={next} compare={current} /> + </div> + ) + } else { + return ( + <div className="calendar-container"> + <CalendarDatePanel { ...props } pos={0} current={current} /> + </div> + ) } -}) \ No newline at end of file +} + +export default Calendar \ No newline at end of file diff --git a/devui/datepicker/components/calendar/types.ts b/devui/datepicker/components/calendar/types.ts index 8294c0eb..e50ed88e 100644 --- a/devui/datepicker/components/calendar/types.ts +++ b/devui/datepicker/components/calendar/types.ts @@ -6,7 +6,7 @@ export type TEventCallback = (date: Date, position?: number) => void export type TDateConfig = { type?: TDatePanelType mode?: TDatePanelMode - current?: Date + current: Date } export type TDateSelectingBase = { diff --git a/devui/datepicker/components/calendar/utils.ts b/devui/datepicker/components/calendar/utils.ts index 9971b446..370f94f4 100644 --- a/devui/datepicker/components/calendar/utils.ts +++ b/devui/datepicker/components/calendar/utils.ts @@ -50,7 +50,7 @@ export const getMonthWeeklyDays = (date: any = new Date()) => { } const { year, month } = getHumanDate(date) const days = getMonthDays(year, month) - const dayRows = [] + const dayRows: TDateCell[][] = [] while (days.length > 0) { dayRows.push(days.splice(0, 7)) } -- Gitee From abbcb39788013ab8133831d0fd13af3207074f23 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Mon, 9 Aug 2021 22:32:55 +0800 Subject: [PATCH 15/42] =?UTF-8?q?feat:=20=E9=83=A8=E5=88=86=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=8E=A5=E5=8F=A3=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/components/input/index.tsx | 56 +++++++------- .../datepicker/components/pop-panel/index.tsx | 73 ++++++++----------- devui/datepicker/datepicker.tsx | 35 ++++++++- sites/components/datepicker/index.md | 23 +++++- 4 files changed, 107 insertions(+), 80 deletions(-) diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx index efcdcabb..182f5ddc 100644 --- a/devui/datepicker/components/input/index.tsx +++ b/devui/datepicker/components/input/index.tsx @@ -1,36 +1,32 @@ -import { defineComponent, ref } from 'vue' +import { ref } from 'vue' import Icon from '../../../icon' - import './index.css' -type TEventCallback = (e: MouseEvent) => void +type TProps = { + width?: number + value?: string + onActive?: (el: Element) => void +} -export default defineComponent({ - name: 'DDatepickerInput', - props: { - width: { type: Number }, - onActive: { type: Function }, - }, - setup(props, ctx) { - const { width = 160, onActive } = props || {} - const container = ref<Element>() - const handleClick = (e: MouseEvent) => { - if(container.value && typeof onActive === 'function') { - onActive(container.value) - } - } - return () => { - return ( - <div - ref={container} - class="datapicker-input-border" - style={{ width: `${width}px` }} - onClick={handleClick} - > - <input class="datapicker-input" type="text" /> - <Icon name="calendar" size="16px" /> - </div> - ) +const DatepickerInput = (props: TProps) => { + const { width = 160, value = '', onActive } = props + const container = ref<Element>() + const handleClick = () => { + if(container.value && typeof onActive === 'function') { + onActive(container.value) } } -}) \ No newline at end of file + return ( + <div + ref={container} + className="datapicker-input-border" + style={{ width: `${width}px` }} + onClick={handleClick} + > + <input className="datapicker-input" type="text" value={value} /> + <Icon name="calendar" size="16px" /> + </div> + ) +} + +export default DatepickerInput \ No newline at end of file diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx index 47f68b76..a4c9eca8 100644 --- a/devui/datepicker/components/pop-panel/index.tsx +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -1,55 +1,40 @@ -import { defineComponent, ref, renderSlot } from 'vue' +import { defineComponent, ref, renderSlot, SetupContext } from 'vue' import './index.css' type TProps = { show: boolean background?: string - border?: string - padding?: string - hostArea?: any xPosition?: 'left' | 'right' yPosition?: 'top' | 'bottom' + xOffset?: number + yOffset?: number } -export default defineComponent({ - name: 'DDatepickerPopPanel', - props: { - show: { type: Boolean }, - xPosition: { type: String }, - yPosition: { type: String }, - xOffset: { type: Number }, - yOffset: { type: Number }, - children: { type: Object } - }, - setup(props, ctx) { - - const container = ref<Element>() - - return () => { - if (!props.show) { - return null - } - - - const { - xPosition = 'left', yPosition = 'top', - xOffset = 0, yOffset = 0, - } = props - - const children: any = renderSlot(ctx.slots, 'default') - - return ( - <div - ref={container} - class="datapicker-pop-panel" - style={{ - left: xPosition === 'left' ? `${xOffset}px` : 'auto', - right: xPosition === 'right' ? `${xOffset}px` : 'auto', - top: yPosition === 'top' ? `${yOffset}px` : 'auto', - bottom: yPosition === 'bottom' ? `${yOffset}px` : 'auto', - }} - >{children}</div>) - } +const PopPanel = (props: TProps, ctx: SetupContext) => { + const container = ref<Element>() + if (!props.show) { + return null } -}) \ No newline at end of file + const { + xPosition = 'left', yPosition = 'top', + xOffset = 0, yOffset = 0, + } = props + + const left = xPosition === 'left' ? `${xOffset}px` : 'auto' + const right = xPosition === 'right' ? `${xOffset}px` : 'auto' + const top = yPosition === 'top' ? `${yOffset}px` : 'auto' + const bottom = yPosition === 'bottom' ? `${yOffset}px` : 'auto' + + const children: any = renderSlot(ctx.slots, 'default') + + return ( + <div + ref={container} + className="datapicker-pop-panel" + style={{ left, right, top, bottom }} + >{children}</div> + ) +} + +export default PopPanel diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index a8bc9b19..ad90e6e9 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -54,6 +54,9 @@ const getHostRange = (host: Element): { export default defineComponent({ name: 'DDatepicker', props: { + type: { type: String, default: 'select' }, + autoComplete: { type: Boolean, default: false }, + onDateChange: { type: Function } }, setup(props, ctx) { const container = ref<Element>() @@ -66,12 +69,14 @@ export default defineComponent({ panelYPos: 'top' | 'bottom' pointX: string pointY: string + value: string }>({ showPanel: false, panelXPos: 'left', panelYPos: 'top', pointX: '0px', pointY: '0px', + value: 'y/MM/dd' }) const dateState = reactive<{ @@ -154,10 +159,32 @@ export default defineComponent({ } } + const handleSelected = (date: Date) => { + dateState.dateStart = date + inputState.value = date.toDateString() + if(props.autoComplete) { + inputState.showPanel = false + } + if(typeof props.onDateChange === 'function') { + props.onDateChange(date) + } + } + + const handleSelectEnd = (date: Date) => { + dateState.dateEnd = date + inputState.value = (dateState.dateStart as Date).toDateString() + ' - ' + date.toDateString() + if(props.autoComplete) { + inputState.showPanel = false + } + if(typeof props.onDateChange === 'function') { + props.onDateChange(date) + } + } + return () => { return ( <div ref={container} class="datapicker-container"> - <Input width={140} onActive={handleActive} /> + <Input width={140} onActive={handleActive} value={inputState.value} /> <div ref={popCont} class="datepicker-pop-container" style={{ left: inputState.pointX, top: inputState.pointY }}> <PopPanel show={inputState.showPanel} @@ -166,7 +193,7 @@ export default defineComponent({ xOffset={0} yOffset={0} ><Calendar - type="range" + type={props.type || 'select'} current={dateState.dateCurrent} next={dateState.dateNext} dateStart={dateState.dateStart} @@ -176,9 +203,9 @@ export default defineComponent({ dateState.dateEnd = dateState.dateHover = undefined dateState.dateStart = date }} - onSelected={(date: Date) => dateState.dateStart = date} + onSelected={handleSelected} onSelectStart={(date: Date) => dateState.dateStart = date} - onSelectEnd={(date: Date) => dateState.dateEnd = date} + onSelectEnd={handleSelectEnd} onSelecting={(date: Date) => dateState.dateHover = date} onPreviousYear={(date: Date, pos: number) => handleSwitch(0, pos, date)} onPreviousMonth={(date: Date, pos: number) => handleSwitch(1, pos, date)} diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index d13aa97f..b9dd6e5b 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -8,10 +8,29 @@ - 选择日期区间 - 限制日期输入 -### 简单使用 +### 日期选择 +```jsx <d-datepicker /> +``` -```jsx <d-datepicker /> + +### 日期区域选择 + +```jsx +<d-datepicker type="range" /> ``` + +<d-datepicker type="range" /> + +### 自动关闭 + +```jsx +<d-datepicker auto-complete /> +<d-datepicker auto-complete type="range" /> +``` + +<d-datepicker auto-complete /> +<d-datepicker auto-complete type="range" /> + -- Gitee From 08517c79c6759ed713f94b2ef8607b702492dce0 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Tue, 10 Aug 2021 08:35:19 +0800 Subject: [PATCH 16/42] feat: date string format --- devui/datepicker/components/input/index.tsx | 5 ++- .../datepicker/components/pop-panel/index.tsx | 3 +- devui/datepicker/datepicker.tsx | 36 ++++++++++++---- devui/datepicker/utils.ts | 43 +++++++++++++++++++ sites/components/datepicker/index.md | 8 ++-- 5 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 devui/datepicker/utils.ts diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx index 182f5ddc..6904d37e 100644 --- a/devui/datepicker/components/input/index.tsx +++ b/devui/datepicker/components/input/index.tsx @@ -5,11 +5,12 @@ import './index.css' type TProps = { width?: number value?: string + placeholder?: string onActive?: (el: Element) => void } const DatepickerInput = (props: TProps) => { - const { width = 160, value = '', onActive } = props + const { width = 160, placeholder = '', value = '', onActive } = props const container = ref<Element>() const handleClick = () => { if(container.value && typeof onActive === 'function') { @@ -23,7 +24,7 @@ const DatepickerInput = (props: TProps) => { style={{ width: `${width}px` }} onClick={handleClick} > - <input className="datapicker-input" type="text" value={value} /> + <input className="datapicker-input" type="text" value={value} placeholder={placeholder} /> <Icon name="calendar" size="16px" /> </div> ) diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx index a4c9eca8..022659da 100644 --- a/devui/datepicker/components/pop-panel/index.tsx +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -3,12 +3,13 @@ import { defineComponent, ref, renderSlot, SetupContext } from 'vue' import './index.css' type TProps = { - show: boolean + show?: boolean background?: string xPosition?: 'left' | 'right' yPosition?: 'top' | 'bottom' xOffset?: number yOffset?: number + children?: any } const PopPanel = (props: TProps, ctx: SetupContext) => { diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index ad90e6e9..7b7146da 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -2,6 +2,7 @@ import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' import Input from './components/input' import PopPanel from './components/pop-panel' import Calendar from './components/calendar' +import { formatDate, formatRange } from './utils' import './datepicker.css' @@ -54,9 +55,11 @@ const getHostRange = (host: Element): { export default defineComponent({ name: 'DDatepicker', props: { - type: { type: String, default: 'select' }, autoComplete: { type: Boolean, default: false }, - onDateChange: { type: Function } + onDateChange: { type: Function }, + range: { type: Boolean, default: false }, + format: { type: String, default: 'y/MM/dd' }, + rangeSpliter: { type: String, default: '-' } }, setup(props, ctx) { const container = ref<Element>() @@ -76,18 +79,18 @@ export default defineComponent({ panelYPos: 'top', pointX: '0px', pointY: '0px', - value: 'y/MM/dd' + value: '' }) const dateState = reactive<{ - type?: 'select' | 'range' + range: boolean dateCurrent?: Date dateNext?: Date dateStart?: Date dateEnd?: Date dateHover?: Date }>({ - type: 'range', + range: false, dateCurrent: new Date(), dateNext: new Date(2021, 8, 2) }) @@ -159,9 +162,22 @@ export default defineComponent({ } } + const setInputValue = () => { + const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} + if(range) { + inputState.value = formatRange(format, + dateState.dateStart, + dateState.dateEnd, + rangeSpliter + ) + } else { + inputState.value = formatDate(format, dateState.dateStart) + } + } + const handleSelected = (date: Date) => { dateState.dateStart = date - inputState.value = date.toDateString() + setInputValue() if(props.autoComplete) { inputState.showPanel = false } @@ -172,7 +188,7 @@ export default defineComponent({ const handleSelectEnd = (date: Date) => { dateState.dateEnd = date - inputState.value = (dateState.dateStart as Date).toDateString() + ' - ' + date.toDateString() + setInputValue() if(props.autoComplete) { inputState.showPanel = false } @@ -182,9 +198,11 @@ export default defineComponent({ } return () => { + const { format = 'y/MM/dd', rangeSpliter = '-' } = props || {} + const placeholder = props.range ? `${format} ${rangeSpliter} ${format}` : format return ( <div ref={container} class="datapicker-container"> - <Input width={140} onActive={handleActive} value={inputState.value} /> + <Input width={140} onActive={handleActive} value={inputState.value} placeholder={placeholder} /> <div ref={popCont} class="datepicker-pop-container" style={{ left: inputState.pointX, top: inputState.pointY }}> <PopPanel show={inputState.showPanel} @@ -193,7 +211,7 @@ export default defineComponent({ xOffset={0} yOffset={0} ><Calendar - type={props.type || 'select'} + type={props.range ? 'range' : 'select'} current={dateState.dateCurrent} next={dateState.dateNext} dateStart={dateState.dateStart} diff --git a/devui/datepicker/utils.ts b/devui/datepicker/utils.ts new file mode 100644 index 00000000..cb3c7e5a --- /dev/null +++ b/devui/datepicker/utils.ts @@ -0,0 +1,43 @@ +const getDateTime = (d: Date) => { + const year = d.getFullYear() + const month = d.getMonth() + 1 + const date = d.getDate() + const day = d.getDay() + const hour = d.getHours() + const minute = d.getMinutes() + const second = d.getSeconds() + const ms = d.getMilliseconds() + return [year, month, date, day, hour, minute, second, ms] +} + +const fixStart = (n: number, m: string, max = 2, ch = '0') => { + return (n + '').padStart(Math.min(m.length, max), ch) +} + +/** + * - y: year yy 取后2位,其他情况取4位 + * - M: month 最多取2位补0 + * @param fmt + * @param d + */ +export const formatDate = (fmt: string, d: Date) => { + const usage = getDateTime(d) + let res = fmt + res = res.replace(/y+/g, m => { + const year = usage[0] + '' + if(m.length === 2) { + return year.substring(2) + } + return year + }) + res = res.replace(/M+/g, m => fixStart(usage[1], m)) + res = res.replace(/d+/g, m => fixStart(usage[2], m)) + res = res.replace(/h+/g, m => fixStart(usage[4], m)) + res = res.replace(/m+/g, m => fixStart(usage[5], m)) + res = res.replace(/s+/g, m => fixStart(usage[6], m)) + return res +} + +export const formatRange = (fmt: string, a: Date, b: Date, conn = '-') => { + return `${formatDate(fmt, a)} ${conn} ${formatDate(fmt, b)}` +} \ No newline at end of file diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index b9dd6e5b..b7b3b5ff 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -19,18 +19,18 @@ ### 日期区域选择 ```jsx -<d-datepicker type="range" /> +<d-datepicker range /> ``` -<d-datepicker type="range" /> +<d-datepicker range /> ### 自动关闭 ```jsx <d-datepicker auto-complete /> -<d-datepicker auto-complete type="range" /> +<d-datepicker auto-complete range /> ``` <d-datepicker auto-complete /> -<d-datepicker auto-complete type="range" /> +<d-datepicker auto-complete range /> -- Gitee From 87def722c7e40afa57f9dcb14bc3c7b3f2f4fd2f Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Tue, 10 Aug 2021 11:44:40 +0800 Subject: [PATCH 17/42] =?UTF-8?q?feat:=201=E3=80=81Datepicker=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E4=B8=BAattach=E6=96=B9=E5=BC=8F=EF=BC=9B2=E3=80=81?= =?UTF-8?q?=E5=AE=BF=E4=B8=BBinput=E6=BB=9A=E5=8A=A8=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E8=B7=9F=E8=B8=AA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/components/panel/index.css | 2 +- .../datepicker/components/calendar/helper.ts | 2 + devui/datepicker/components/calendar/types.ts | 1 + devui/datepicker/datepicker-old.tsx | 238 ++++++++++++ devui/datepicker/datepicker.css | 24 +- devui/datepicker/datepicker.tsx | 343 ++++++++++-------- sites/components/datepicker/index.md | 28 +- 7 files changed, 455 insertions(+), 183 deletions(-) create mode 100644 devui/datepicker/datepicker-old.tsx diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/calendar/components/panel/index.css index 67254d45..02c44b35 100644 --- a/devui/datepicker/components/calendar/components/panel/index.css +++ b/devui/datepicker/components/calendar/components/panel/index.css @@ -1,6 +1,6 @@ .calendar-panel { - border: 1px solid #dddddd; + border: 0 solid #dddddd; padding: 5px; width: 240px; box-sizing: border-box; diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts index 3ce47850..eaf9465a 100644 --- a/devui/datepicker/components/calendar/helper.ts +++ b/devui/datepicker/components/calendar/helper.ts @@ -41,11 +41,13 @@ export const trigEvent = (props: TDatePanelProps, day: TDateCell): void => { invokeCallback(props.onSelectStart, day.date) } else if (!props.dateEnd) { invokeCallback(props.onSelectEnd, day.date) + typeof props.onChange === 'function' && props.onChange(props.type, props) } else { invokeCallback(props.onReset, day.date) } } else { invokeCallback(props.onSelected, day.date) + typeof props.onChange === 'function' && props.onChange(props.type, props) } } diff --git a/devui/datepicker/components/calendar/types.ts b/devui/datepicker/components/calendar/types.ts index e50ed88e..9b20851d 100644 --- a/devui/datepicker/components/calendar/types.ts +++ b/devui/datepicker/components/calendar/types.ts @@ -35,6 +35,7 @@ export type TDatePanelEventProps = TDateToolbarEventProps & { onSelectStart?: TEventCallback onSelectEnd?: TEventCallback onSelecting?: TEventCallback + onChange?: (type: TDatePanelType, config: TDateSelectingBase) => void } export type TDatePanelDataProps = TDateToolbarDataProps & TDateSelectingBase diff --git a/devui/datepicker/datepicker-old.tsx b/devui/datepicker/datepicker-old.tsx new file mode 100644 index 00000000..7b7146da --- /dev/null +++ b/devui/datepicker/datepicker-old.tsx @@ -0,0 +1,238 @@ +import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' +import Input from './components/input' +import PopPanel from './components/pop-panel' +import Calendar from './components/calendar' +import { formatDate, formatRange } from './utils' + +import './datepicker.css' + +const isIn = (start: Node | null, target: Node | null) => { + if(!target) { + return false + } + while(start) { + if(start === target) { + return true + } + start = start.parentNode + } + return false +} + +const factoryAutoClosePanel = (cont: Element, cb: () => void) => (e: MouseEvent) => { + const { target } = e + if(isIn(target as Node, cont)) { + return + } + cb() +} + +const attachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { + el.addEventListener(name, cb, capture) + return { el, name, cb, capture } +} + +const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { + el.removeEventListener(name, cb, capture) +} + +const getHostRange = (host: Element): { + left: number + right: number + top: number + bottom: number + width: number + height: number +} => { + const { left, top, width, height } = host.getBoundingClientRect() + const right = window.innerWidth - left - width + const bottom = window.innerHeight - top - height + + // console.log(left, right, top, bottom) + return { left, right, top, bottom, width, height } +} + +export default defineComponent({ + name: 'DDatepicker', + props: { + autoComplete: { type: Boolean, default: false }, + onDateChange: { type: Function }, + range: { type: Boolean, default: false }, + format: { type: String, default: 'y/MM/dd' }, + rangeSpliter: { type: String, default: '-' } + }, + setup(props, ctx) { + const container = ref<Element>() + const popCont = ref<Element>() + const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] + + const inputState = reactive<{ + showPanel: boolean + panelXPos: 'left' | 'right' + panelYPos: 'top' | 'bottom' + pointX: string + pointY: string + value: string + }>({ + showPanel: false, + panelXPos: 'left', + panelYPos: 'top', + pointX: '0px', + pointY: '0px', + value: '' + }) + + const dateState = reactive<{ + range: boolean + dateCurrent?: Date + dateNext?: Date + dateStart?: Date + dateEnd?: Date + dateHover?: Date + }>({ + range: false, + dateCurrent: new Date(), + dateNext: new Date(2021, 8, 2) + }) + + onMounted(() => { + const { value: cont } = container + if(!cont) { + return + } + + const handleAutoClosePanel = factoryAutoClosePanel(cont, () => { + inputState.showPanel = false + }) + events.push(attachEvent(document, 'click', handleAutoClosePanel, false)) + // // 窗口失焦点时隐藏弹窗 + // events.push(attachEvent(window, 'blur', () => { state.showPanel = false }, false)) + }) + + onUnmounted(() => { + events.forEach(({ el, cb, name, capture }) => detachEvent(el, name, cb, capture)) + events.splice(0, events.length) + }) + + const handleActive = (e: Element) => { + if(inputState.showPanel) { + return + } + const range = getHostRange(e) + if(range.left > range.right) { + inputState.panelXPos = 'right' + inputState.pointX = `${range.width}px` + } else { + inputState.panelXPos = 'left' + inputState.pointX = '0px' + } + + if(range.top > range.bottom) { + inputState.panelYPos = 'bottom' + inputState.pointY = '0px' + } else { + inputState.panelYPos = 'top' + inputState.pointY = `${range.height}px` + } + inputState.showPanel = true + } + + const handleSwitch = (index: number, pos: number, date: Date) => { + switch(index) { + case 0: // previous year + const preYear = new Date(date) + preYear.setFullYear(preYear.getFullYear() - 1) + pos === 0 ? (dateState.dateCurrent = preYear) : (dateState.dateNext = preYear) + break + case 1: // previous month + const preMonth = new Date(date) + preMonth.setMonth(preMonth.getMonth() - 1) + pos === 0 ? (dateState.dateCurrent = preMonth) : (dateState.dateNext = preMonth) + break + case 2: // next month + const nextMonth = new Date(date) + nextMonth.setMonth(nextMonth.getMonth() + 1) + pos === 0 ? (dateState.dateCurrent = nextMonth) : (dateState.dateNext = nextMonth) + break + case 3: // next year + const nextYear = new Date(date) + nextYear.setFullYear(nextYear.getFullYear() + 1) + pos === 0 ? (dateState.dateCurrent = nextYear) : (dateState.dateNext = nextYear) + break + } + } + + const setInputValue = () => { + const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} + if(range) { + inputState.value = formatRange(format, + dateState.dateStart, + dateState.dateEnd, + rangeSpliter + ) + } else { + inputState.value = formatDate(format, dateState.dateStart) + } + } + + const handleSelected = (date: Date) => { + dateState.dateStart = date + setInputValue() + if(props.autoComplete) { + inputState.showPanel = false + } + if(typeof props.onDateChange === 'function') { + props.onDateChange(date) + } + } + + const handleSelectEnd = (date: Date) => { + dateState.dateEnd = date + setInputValue() + if(props.autoComplete) { + inputState.showPanel = false + } + if(typeof props.onDateChange === 'function') { + props.onDateChange(date) + } + } + + return () => { + const { format = 'y/MM/dd', rangeSpliter = '-' } = props || {} + const placeholder = props.range ? `${format} ${rangeSpliter} ${format}` : format + return ( + <div ref={container} class="datapicker-container"> + <Input width={140} onActive={handleActive} value={inputState.value} placeholder={placeholder} /> + <div ref={popCont} class="datepicker-pop-container" style={{ left: inputState.pointX, top: inputState.pointY }}> + <PopPanel + show={inputState.showPanel} + xPosition={inputState.panelXPos} + yPosition={inputState.panelYPos} + xOffset={0} + yOffset={0} + ><Calendar + type={props.range ? 'range' : 'select'} + current={dateState.dateCurrent} + next={dateState.dateNext} + dateStart={dateState.dateStart} + dateEnd={dateState.dateEnd} + dateHover={dateState.dateHover} + onReset={(date: Date) => { + dateState.dateEnd = dateState.dateHover = undefined + dateState.dateStart = date + }} + onSelected={handleSelected} + onSelectStart={(date: Date) => dateState.dateStart = date} + onSelectEnd={handleSelectEnd} + onSelecting={(date: Date) => dateState.dateHover = date} + onPreviousYear={(date: Date, pos: number) => handleSwitch(0, pos, date)} + onPreviousMonth={(date: Date, pos: number) => handleSwitch(1, pos, date)} + onNextMonth={(date: Date, pos: number) => handleSwitch(2, pos, date)} + onNextYear={(date: Date, pos: number) => handleSwitch(3, pos, date)} + /></PopPanel> + </div> + </div> + ) + } + } +}) \ No newline at end of file diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.css index f1ad1756..020548dc 100644 --- a/devui/datepicker/datepicker.css +++ b/devui/datepicker/datepicker.css @@ -1,13 +1,19 @@ -.datapicker-container { - margin: 0; - padding: 0; - position: relative; +.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; } -.datepicker-pop-container { +.datepicker-container { + margin: 0; + padding: 0; position: absolute; - width: 0; - height: 0; - left: 0; - top: 0; + border: 1px solid #cccccc; + z-index: 99; + background-color: #ffffff; } diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 7b7146da..b9e642b6 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,11 +1,19 @@ -import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' -import Input from './components/input' -import PopPanel from './components/pop-panel' -import Calendar from './components/calendar' +import { defineComponent, reactive, onMounted, onUnmounted, ref } from 'vue' import { formatDate, formatRange } from './utils' - +import Calendar from './components/calendar' import './datepicker.css' +type TState = { + range?: boolean + current?: Date + next?: Date + start?: Date + end?: Date + hover?: Date + show?: boolean + input?: string +} + const isIn = (start: Node | null, target: Node | null) => { if(!target) { return false @@ -36,200 +44,213 @@ const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capt el.removeEventListener(name, cb, capture) } -const getHostRange = (host: Element): { - left: number - right: number - top: number - bottom: number - width: number - height: number -} => { - const { left, top, width, height } = host.getBoundingClientRect() - const right = window.innerWidth - left - width - const bottom = window.innerHeight - top - height - - // console.log(left, right, top, bottom) - return { left, right, top, bottom, width, height } +const handleCalendarSwitchState = (state: TState, index: number, pos: number, date: Date) => { + switch(index) { + case 0: // previous year + const preYear = new Date(date) + preYear.setFullYear(preYear.getFullYear() - 1) + pos === 0 ? (state.current = preYear) : (state.next = preYear) + break + case 1: // previous month + const preMonth = new Date(date) + preMonth.setMonth(preMonth.getMonth() - 1) + pos === 0 ? (state.current = preMonth) : (state.next = preMonth) + break + case 2: // next month + const nextMonth = new Date(date) + nextMonth.setMonth(nextMonth.getMonth() + 1) + pos === 0 ? (state.current = nextMonth) : (state.next = nextMonth) + break + case 3: // next year + const nextYear = new Date(date) + nextYear.setFullYear(nextYear.getFullYear() + 1) + pos === 0 ? (state.current = nextYear) : (state.next = nextYear) + break + } +} + +const formatOutputValue = (state: TState, props: any) => { + const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} + if(range) { + return formatRange(format, + state.start, + state.end, + rangeSpliter + ) + } else { + return formatDate(format, state.start) + } +} + +const handleDomOutput = (id: string | undefined, output: string) => { + if(id && typeof id === 'string') { + const el = document.querySelector(id) + if(el instanceof HTMLInputElement) { + el.value = output + } + } +} + +const invokeFunction = (fn: any, ...args: any[]) => { + if(typeof fn === 'function') { + fn(...args) + } +} + +const traceScroll = (el: Node, callback: (e: Event) => void) => { + const cb = (e: Event) => { + typeof callback === 'function' && callback(e) + } + const els: Node[] = [], name = 'scroll' + while(el.parentNode) { + els.push(el.parentNode) + el.parentNode.addEventListener(name, cb) + el = el.parentNode + } + return { elements: els, callback: cb, name } } export default defineComponent({ name: 'DDatepicker', props: { - autoComplete: { type: Boolean, default: false }, - onDateChange: { type: Function }, + selectedDateChange: { type: Function }, + autoClose: { type: Boolean, default: false }, range: { type: Boolean, default: false }, format: { type: String, default: 'y/MM/dd' }, - rangeSpliter: { type: String, default: '-' } + rangeSpliter: { type: String, default: '-' }, + attachInputDom: { type: String } }, setup(props, ctx) { + const container = ref<Element>() - const popCont = ref<Element>() const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] - const inputState = reactive<{ - showPanel: boolean - panelXPos: 'left' | 'right' - panelYPos: 'top' | 'bottom' - pointX: string - pointY: string - value: string - }>({ - showPanel: false, - panelXPos: 'left', - panelYPos: 'top', - pointX: '0px', - pointY: '0px', - value: '' + const state = reactive<TState>({ + range: !!props.range, + current: new Date(), + show: false, }) - const dateState = reactive<{ - range: boolean - dateCurrent?: Date - dateNext?: Date - dateStart?: Date - dateEnd?: Date - dateHover?: Date + const pos = reactive<{ + x: string + y: string }>({ - range: false, - dateCurrent: new Date(), - dateNext: new Date(2021, 8, 2) + x: '0', + y: '0', }) - onMounted(() => { - const { value: cont } = container - if(!cont) { - return + const getAttachInputDom = () => { + const { attachInputDom } = props || {} + if(!attachInputDom || typeof attachInputDom !== 'string') { + return null } - - const handleAutoClosePanel = factoryAutoClosePanel(cont, () => { - inputState.showPanel = false - }) - events.push(attachEvent(document, 'click', handleAutoClosePanel, false)) - // // 窗口失焦点时隐藏弹窗 - // events.push(attachEvent(window, 'blur', () => { state.showPanel = false }, false)) - }) - - onUnmounted(() => { - events.forEach(({ el, cb, name, capture }) => detachEvent(el, name, cb, capture)) - events.splice(0, events.length) - }) + const el = document.querySelector(attachInputDom) + if(!el) { + return null + } + return el + } - const handleActive = (e: Element) => { - if(inputState.showPanel) { + const handlePosition = () => { + if(!state.show) { + pos.x = `-100%` + pos.y = `-100%` return } - const range = getHostRange(e) - if(range.left > range.right) { - inputState.panelXPos = 'right' - inputState.pointX = `${range.width}px` - } else { - inputState.panelXPos = 'left' - inputState.pointX = '0px' + const el = getAttachInputDom() + if(!el) { + return } - - if(range.top > range.bottom) { - inputState.panelYPos = 'bottom' - inputState.pointY = '0px' + const { left, top, width, height } = el.getBoundingClientRect() + const { width: _width, height: _height } = container.value.getBoundingClientRect() + const bottom = window.innerHeight - top - height + pos.x = `${left}px` + if(bottom > top) { + pos.y = `${top + height}px` } else { - inputState.panelYPos = 'top' - inputState.pointY = `${range.height}px` + pos.y = `${top - _height}px` } - inputState.showPanel = true + } - const handleSwitch = (index: number, pos: number, date: Date) => { - switch(index) { - case 0: // previous year - const preYear = new Date(date) - preYear.setFullYear(preYear.getFullYear() - 1) - pos === 0 ? (dateState.dateCurrent = preYear) : (dateState.dateNext = preYear) - break - case 1: // previous month - const preMonth = new Date(date) - preMonth.setMonth(preMonth.getMonth() - 1) - pos === 0 ? (dateState.dateCurrent = preMonth) : (dateState.dateNext = preMonth) - break - case 2: // next month - const nextMonth = new Date(date) - nextMonth.setMonth(nextMonth.getMonth() + 1) - pos === 0 ? (dateState.dateCurrent = nextMonth) : (dateState.dateNext = nextMonth) - break - case 3: // next year - const nextYear = new Date(date) - nextYear.setFullYear(nextYear.getFullYear() + 1) - pos === 0 ? (dateState.dateCurrent = nextYear) : (dateState.dateNext = nextYear) - break + const handleAttachInputDom = () => { + const el = getAttachInputDom() + if(!el) { + state.show = true + return } - } - const setInputValue = () => { - const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} - if(range) { - inputState.value = formatRange(format, - dateState.dateStart, - dateState.dateEnd, - rangeSpliter - ) - } else { - inputState.value = formatDate(format, dateState.dateStart) + if(el instanceof HTMLInputElement) { + const format = props.format || `y/MM/dd` + const sp = props.rangeSpliter || '-' + el.placeholder = props.range ? `${format} ${sp} ${format}` : format } + state.show = false + state.input = props.attachInputDom + events.push(attachEvent(el, 'click', () => !state.show && (state.show = true), false)) + events.push(attachEvent(document, 'click', (e: MouseEvent) => { + if(!state.show || e.target === el || isIn(e.target as Node, container.value)) { + return + } + state.show = false + }, false)) + const tracing = traceScroll(el, () => { + // console.log(111) + handlePosition() + }) + tracing.elements.forEach(node => { + events.push({ el: node, cb: tracing.callback, name: tracing.name, capture: false }) + }) } - const handleSelected = (date: Date) => { - dateState.dateStart = date - setInputValue() - if(props.autoComplete) { - inputState.showPanel = false - } - if(typeof props.onDateChange === 'function') { - props.onDateChange(date) - } - } + onMounted(() => { + handleAttachInputDom() + }) - const handleSelectEnd = (date: Date) => { - dateState.dateEnd = date - setInputValue() - if(props.autoComplete) { - inputState.showPanel = false - } - if(typeof props.onDateChange === 'function') { - props.onDateChange(date) - } - } + onUnmounted(() => { + events.forEach(({ el, cb, name, capture }) => detachEvent(el, name, cb, capture)) + events.splice(0, events.length) + }) return () => { - const { format = 'y/MM/dd', rangeSpliter = '-' } = props || {} - const placeholder = props.range ? `${format} ${rangeSpliter} ${format}` : format + handlePosition() return ( - <div ref={container} class="datapicker-container"> - <Input width={140} onActive={handleActive} value={inputState.value} placeholder={placeholder} /> - <div ref={popCont} class="datepicker-pop-container" style={{ left: inputState.pointX, top: inputState.pointY }}> - <PopPanel - show={inputState.showPanel} - xPosition={inputState.panelXPos} - yPosition={inputState.panelYPos} - xOffset={0} - yOffset={0} - ><Calendar + <div class="datepicker-global-viewport"> + <div + ref={container} + class="datepicker-container" + style={{ + transform: `translateX(${pos.x}) translateY(${pos.y})` + }} + > + <Calendar type={props.range ? 'range' : 'select'} - current={dateState.dateCurrent} - next={dateState.dateNext} - dateStart={dateState.dateStart} - dateEnd={dateState.dateEnd} - dateHover={dateState.dateHover} + current={state.current} + next={state.next} + dateStart={state.start} + dateEnd={state.end} + dateHover={state.hover} onReset={(date: Date) => { - dateState.dateEnd = dateState.dateHover = undefined - dateState.dateStart = date + state.end = state.hover = undefined + state.start = date + }} + onChange={() => { + const output = formatOutputValue(state, props) + handleDomOutput(state.input, output) + invokeFunction(props.selectedDateChange, output) + if(props.autoClose) { + state.show = false + } }} - onSelected={handleSelected} - onSelectStart={(date: Date) => dateState.dateStart = date} - onSelectEnd={handleSelectEnd} - onSelecting={(date: Date) => dateState.dateHover = date} - onPreviousYear={(date: Date, pos: number) => handleSwitch(0, pos, date)} - onPreviousMonth={(date: Date, pos: number) => handleSwitch(1, pos, date)} - onNextMonth={(date: Date, pos: number) => handleSwitch(2, pos, date)} - onNextYear={(date: Date, pos: number) => handleSwitch(3, pos, date)} - /></PopPanel> + onSelected={(date: Date) => state.start = date} + onSelectStart={(date: Date) => state.start = date} + onSelectEnd={(date: Date) => state.end = date} + 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)} + onNextMonth={(date: Date, pos: number) => handleCalendarSwitchState(state, 2, pos, date)} + onNextYear={(date: Date, pos: number) => handleCalendarSwitchState(state, 3, pos, date)} + /> </div> </div> ) diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index b7b3b5ff..facd7d1a 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -11,26 +11,30 @@ ### 日期选择 ```jsx -<d-datepicker /> +<input type="text" id="datepicker-input" /> +<input type="text" id="datepicker-input-autoclose" /> +<d-datepicker attach-input-dom="#datepicker-input" /> +<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> ``` -<d-datepicker /> +<input type="text" id="datepicker-input" /> +<input type="text" id="datepicker-input-autoclose" /> +<d-datepicker attach-input-dom="#datepicker-input" /> +<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> -### 日期区域选择 +### 区域选择 ```jsx -<d-datepicker range /> +<input type="text" id="datepicker-input-range" /> +<d-datepicker range attach-input-dom="#datepicker-input-range" /> ``` -<d-datepicker range /> +<input type="text" id="datepicker-input-range" /> +<d-datepicker range attach-input-dom="#datepicker-input-range" /> + + +### Scroll位置跟踪 -### 自动关闭 -```jsx -<d-datepicker auto-complete /> -<d-datepicker auto-complete range /> -``` -<d-datepicker auto-complete /> -<d-datepicker auto-complete range /> -- Gitee From 1772a03d223183f0dda901e4249e161adc91ef00 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Tue, 10 Aug 2021 15:38:53 +0800 Subject: [PATCH 18/42] =?UTF-8?q?feat:=20=E6=95=B4=E7=90=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/datepicker.tsx | 133 +++++++++++++++----------------- devui/datepicker/utils.ts | 58 +++++++++++++- 2 files changed, 118 insertions(+), 73 deletions(-) diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index b9e642b6..7ea2c56b 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,5 +1,9 @@ import { defineComponent, reactive, onMounted, onUnmounted, ref } from 'vue' -import { formatDate, formatRange } from './utils' +import { + EventManager, + formatDate, formatRange, isIn, + traceNode, invokeFunction, +} from './utils' import Calendar from './components/calendar' import './datepicker.css' @@ -14,36 +18,13 @@ type TState = { input?: string } -const isIn = (start: Node | null, target: Node | null) => { - if(!target) { - return false - } - while(start) { - if(start === target) { - return true - } - start = start.parentNode - } - return false -} - -const factoryAutoClosePanel = (cont: Element, cb: () => void) => (e: MouseEvent) => { - const { target } = e - if(isIn(target as Node, cont)) { - return - } - cb() -} - -const attachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { - el.addEventListener(name, cb, capture) - return { el, name, cb, capture } -} - -const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { - el.removeEventListener(name, cb, capture) -} - +/** + * Calendar 面板年月切换逻辑 + * @param state + * @param index + * @param pos + * @param date + */ const handleCalendarSwitchState = (state: TState, index: number, pos: number, date: Date) => { switch(index) { case 0: // previous year @@ -69,6 +50,12 @@ const handleCalendarSwitchState = (state: TState, index: number, pos: number, da } } +/** + * 格式化日期 + * @param state + * @param props + * @returns + */ const formatOutputValue = (state: TState, props: any) => { const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} if(range) { @@ -82,6 +69,18 @@ const formatOutputValue = (state: TState, props: any) => { } } +const getPlaceholder = (props: any) => { + if(!props) return '' + const format = props.format || `y/MM/dd` + const sp = props.rangeSpliter || '-' + return props.range ? `${format} ${sp} ${format}` : format +} + +/** + * 输出日期选择结果 + * @param id + * @param output + */ const handleDomOutput = (id: string | undefined, output: string) => { if(id && typeof id === 'string') { const el = document.querySelector(id) @@ -91,25 +90,6 @@ const handleDomOutput = (id: string | undefined, output: string) => { } } -const invokeFunction = (fn: any, ...args: any[]) => { - if(typeof fn === 'function') { - fn(...args) - } -} - -const traceScroll = (el: Node, callback: (e: Event) => void) => { - const cb = (e: Event) => { - typeof callback === 'function' && callback(e) - } - const els: Node[] = [], name = 'scroll' - while(el.parentNode) { - els.push(el.parentNode) - el.parentNode.addEventListener(name, cb) - el = el.parentNode - } - return { elements: els, callback: cb, name } -} - export default defineComponent({ name: 'DDatepicker', props: { @@ -123,12 +103,13 @@ export default defineComponent({ setup(props, ctx) { const container = ref<Element>() - const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] + const evtman = new EventManager() const state = reactive<TState>({ range: !!props.range, current: new Date(), show: false, + input: props.attachInputDom, }) const pos = reactive<{ @@ -139,6 +120,10 @@ export default defineComponent({ y: '0', }) + /** + * 获取绑定节点 + * @returns + */ const getAttachInputDom = () => { const { attachInputDom } = props || {} if(!attachInputDom || typeof attachInputDom !== 'string') { @@ -151,6 +136,10 @@ export default defineComponent({ return el } + /** + * 绑定弹出层场景,计算弹出层位置。 + * @returns + */ const handlePosition = () => { if(!state.show) { pos.x = `-100%` @@ -173,49 +162,49 @@ export default defineComponent({ } - const handleAttachInputDom = () => { + onMounted(() => { + // 获取绑定节点(默认input) const el = getAttachInputDom() + // 绑定节点不存在,作为普通组件展示。 if(!el) { + // 显示组件 state.show = true return + } else { + // 作为弹出层,先隐藏 + state.show = false } + // 判断节点原生DOM类型 + // 对input节点的值处理 if(el instanceof HTMLInputElement) { - const format = props.format || `y/MM/dd` - const sp = props.rangeSpliter || '-' - el.placeholder = props.range ? `${format} ${sp} ${format}` : format + // 设置水印文字 + el.placeholder = getPlaceholder(props) } - state.show = false - state.input = props.attachInputDom - events.push(attachEvent(el, 'click', () => !state.show && (state.show = true), false)) - events.push(attachEvent(document, 'click', (e: MouseEvent) => { + + // 绑定节点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 - }, false)) - const tracing = traceScroll(el, () => { - // console.log(111) - handlePosition() }) - tracing.elements.forEach(node => { - events.push({ el: node, cb: tracing.callback, name: tracing.name, capture: false }) + // 对绑定节点做scroll跟踪,并绑定跟踪事件 + traceNode(el).forEach(node => { + evtman.append(node, 'scroll', handlePosition) }) - } - - onMounted(() => { - handleAttachInputDom() }) onUnmounted(() => { - events.forEach(({ el, cb, name, capture }) => detachEvent(el, name, cb, capture)) - events.splice(0, events.length) + evtman.dispose() }) return () => { handlePosition() return ( - <div class="datepicker-global-viewport"> + <div className="datepicker-global-viewport"> <div ref={container} class="datepicker-container" diff --git a/devui/datepicker/utils.ts b/devui/datepicker/utils.ts index cb3c7e5a..8d6b971e 100644 --- a/devui/datepicker/utils.ts +++ b/devui/datepicker/utils.ts @@ -25,7 +25,7 @@ export const formatDate = (fmt: string, d: Date) => { let res = fmt res = res.replace(/y+/g, m => { const year = usage[0] + '' - if(m.length === 2) { + if (m.length === 2) { return year.substring(2) } return year @@ -40,4 +40,60 @@ export const formatDate = (fmt: string, d: Date) => { export const formatRange = (fmt: string, a: Date, b: Date, conn = '-') => { return `${formatDate(fmt, a)} ${conn} ${formatDate(fmt, b)}` +} + +/** + * 判断节点a是否在节点b中 + * @param a + * @param b + * @returns + */ +export const isIn = (a: Node | null, b: Node | null) => { + if (!b) { + return false + } + while (a) { + if (a === b) { + return true + } + a = a.parentNode + } + return false +} + +type EventItem = { el: Node | Window; cb: (...args: any[]) => any; name: string; capture: boolean; } +export class EventManager { + private readonly items: EventItem[] + constructor() { + this.items = [] + } + + append(el: Node | Window, name: string, cb: (...args: any[]) => any, capture = false) { + el.addEventListener(name, cb, capture) + this.items.push({ el, name, cb, capture }) + } + + dispose() { + this.items.splice(0, this.items.length).forEach(({ el, name, cb, capture }) => { + el.removeEventListener(name, cb, capture) + }) + } +} + +export const traceNode = (el: Node) => { + const els: Node[] = [], name = 'scroll' + while (el.parentNode) { + els.push(el.parentNode) + el = el.parentNode + } + return els +} + +/** + * 函数安全调用 + */ +export const invokeFunction = (fn: any, ...args: any[]) => { + if (typeof fn === 'function') { + fn(...args) + } } \ No newline at end of file -- Gitee From c6d58e03a95393fee0594cb3c4bf30f8ec579aa5 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Tue, 10 Aug 2021 20:02:16 +0800 Subject: [PATCH 19/42] =?UTF-8?q?feat:=20=E7=BB=84=E4=BB=B6=E5=9C=A8?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E5=9C=BA=E6=99=AF=E4=B8=8B=E7=9A=84=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/datepicker.css | 4 ++-- devui/datepicker/datepicker.tsx | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.css index 020548dc..4eb33598 100644 --- a/devui/datepicker/datepicker.css +++ b/devui/datepicker/datepicker.css @@ -12,8 +12,8 @@ .datepicker-container { margin: 0; padding: 0; - position: absolute; + position: relative; + display: inline-block; border: 1px solid #cccccc; - z-index: 99; background-color: #ffffff; } diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 7ea2c56b..e0a6dc48 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -16,6 +16,7 @@ type TState = { hover?: Date show?: boolean input?: string + st?: boolean } /** @@ -110,6 +111,7 @@ export default defineComponent({ current: new Date(), show: false, input: props.attachInputDom, + st: true }) const pos = reactive<{ @@ -133,6 +135,7 @@ export default defineComponent({ if(!el) { return null } + state.st = false return el } @@ -204,12 +207,12 @@ export default defineComponent({ return () => { handlePosition() return ( - <div className="datepicker-global-viewport"> + <div className={state.st ? `` : `datepicker-global-viewport`}> <div ref={container} class="datepicker-container" style={{ - transform: `translateX(${pos.x}) translateY(${pos.y})` + transform: state.st ? '' : `translateX(${pos.x}) translateY(${pos.y})` }} > <Calendar -- Gitee From 882523c22dcbd270dc0007cb429f8f7fe6a2971a Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Tue, 10 Aug 2021 20:03:08 +0800 Subject: [PATCH 20/42] =?UTF-8?q?feat:=20=E6=97=A5=E6=9C=9F=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E7=BB=9F=E4=B8=80=E5=A1=AB=E6=BB=A1=E4=B8=BA7*6?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/components/calendar/utils.ts | 45 ++++++++++++++++--- devui/datepicker/utils.ts | 6 ++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/devui/datepicker/components/calendar/utils.ts b/devui/datepicker/components/calendar/utils.ts index 370f94f4..280fae28 100644 --- a/devui/datepicker/components/calendar/utils.ts +++ b/devui/datepicker/components/calendar/utils.ts @@ -38,8 +38,17 @@ const getMonthDays = (year: number, month: number) => { } day = last.getDay() + let tail: Date = last for (let i = day; i < 6; i++) { - dates.push({ date: new Date(year, month, i - day + 1), current: 1 }) + tail = new Date(year, month, i - day + 1) + dates.push({ date: tail, current: 1 }) + } + if(dates.length < 42) { + day = tail.getDate() + for (let i = 1; i <= 7; i++) { + tail = new Date(year, month, day + i) + dates.push({ date: tail, current: 1 }) + } } return dates } @@ -63,7 +72,30 @@ export const invokeCallback = (cb: any, ...args: any[]) => { typeof cb === 'function' && cb(...args) } -export /** +/** + * a - b 的月数 + */ +export const subDateMonth = (a: Date, b: Date) => { + const am = a.getFullYear() * 12 + a.getMonth() + const bm = b.getFullYear() * 12 + b.getMonth() + return am - bm +} + +const ONE_DAY = 1000 * 60 * 60 * 24 + +/** + * a - b 的天数 + * @param a + * @param b + * @returns + */ +export const subDateDay = (a: Date, b: Date) => { + const ad = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime() + const bd = new Date(b.getFullYear(), b.getMonth(), b.getDate()).getTime() + return (ad - bd) / ONE_DAY +} + +/** * 比较日期单位 * @param small 相对早的日期 * @param big 相对晚的日期 @@ -71,15 +103,14 @@ export /** * @param min 不能小于这个值 * @returns */ -const compareDate = (small: Date | undefined, big: Date | undefined, mode: 'year' | 'month', min: number) => { +export const compareDate = (small: Date | undefined, big: Date | undefined, mode: 'year' | 'month', min: number) => { if (!small || !big) { return true } if (mode === 'year') { return big.getFullYear() - small.getFullYear() > min } else { - const bigMonth = big.getFullYear() * 12 + big.getMonth() - const smallMonth = small.getFullYear() * 12 + small.getMonth() - return bigMonth - smallMonth > min + return subDateMonth(big, small) > min } -} \ No newline at end of file +} + diff --git a/devui/datepicker/utils.ts b/devui/datepicker/utils.ts index 8d6b971e..aebc7b07 100644 --- a/devui/datepicker/utils.ts +++ b/devui/datepicker/utils.ts @@ -39,7 +39,11 @@ export const formatDate = (fmt: string, d: Date) => { } export const formatRange = (fmt: string, a: Date, b: Date, conn = '-') => { - return `${formatDate(fmt, a)} ${conn} ${formatDate(fmt, b)}` + const ab = [a, b] + if(a.getTime() > b.getTime()) { + ab.reverse() + } + return `${formatDate(fmt, ab[0])} ${conn} ${formatDate(fmt, ab[1])}` } /** -- Gitee From 0f8af1c09de3edd82d6a6239b744a647994fdecd Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Tue, 10 Aug 2021 20:03:23 +0800 Subject: [PATCH 21/42] =?UTF-8?q?feat:=20=E6=96=87=E6=A1=A3demo=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sites/components/datepicker/index.md | 117 ++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 13 deletions(-) diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index facd7d1a..6e91d2d5 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -1,40 +1,131 @@ # DatePicker 日期选择器 -日期、时间的可视化输入。 +日期、时间可视化输入。 -### 何时使用 +### 作为UI组件 -- 可视化操作日期选择 -- 选择日期区间 -- 限制日期输入 +```jsx +// 默认 range=false +<d-datepicker /> +``` + +<d-datepicker /> + +```jsx +// 日期区间选择 range=true +<d-datepicker range /> +``` + +<d-datepicker range /> + +### 绑定原生`<input>` -### 日期选择 +暂定通过`querySelector`查找节点,绑定真实`dom`节点。此方案待定。 ```jsx -<input type="text" id="datepicker-input" /> -<input type="text" id="datepicker-input-autoclose" /> +// 选取完成后保留弹层 +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input" /> <d-datepicker attach-input-dom="#datepicker-input" /> -<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> ``` -<input type="text" id="datepicker-input" /> -<input type="text" id="datepicker-input-autoclose" /> +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input" /> <d-datepicker attach-input-dom="#datepicker-input" /> + +```jsx +// auto-close=true 选取完成后自动关闭 +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-autoclose" /> +<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> +``` + +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-autoclose" /> <d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> ### 区域选择 ```jsx -<input type="text" id="datepicker-input-range" /> +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-range" /> <d-datepicker range attach-input-dom="#datepicker-input-range" /> ``` -<input type="text" id="datepicker-input-range" /> +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-range" /> <d-datepicker range attach-input-dom="#datepicker-input-range" /> ### Scroll位置跟踪 +在对宿主`<input>`绑定的过程中,对宿主的父级节点做了`scroll`追踪;当任何一级发生`scroll`时,都会更新弹出层位置,确保能让弹出层与`<input>`正确贴合。 + +这个做法是根据`ng`的组件效果实现的。 + +TODO: 跟踪节流。 + +```jsx +<div style="border:1px solid #aaa;height:300px;overflow:auto;"> + <br /> + // ... + <i>占行</i> + // ... + <input style="margin-left:100px;width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-tracing" /> + <d-datepicker auto-close range attach-input-dom="#datepicker-input-tracing" /> + // ... + <br /> + <i>占行</i> + // ... +</div> +``` +<div style="border:1px solid #aaa;height:500px;overflow:auto;"> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <input style="margin-left:100px;width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-tracing" /> + <d-datepicker auto-close range attach-input-dom="#datepicker-input-tracing" /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <i>占行</i> +</div> -- Gitee From 201ddf95ea4aa03ac3f806ce50f859ae94e2ed58 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Tue, 10 Aug 2021 20:19:28 +0800 Subject: [PATCH 22/42] =?UTF-8?q?feat:=20=E6=95=B0=E6=8D=AE=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/datepicker.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index e0a6dc48..7590232a 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -99,16 +99,19 @@ export default defineComponent({ range: { type: Boolean, default: false }, format: { type: String, default: 'y/MM/dd' }, rangeSpliter: { type: String, default: '-' }, - attachInputDom: { type: String } + attachInputDom: { type: String }, }, setup(props, ctx) { const container = ref<Element>() const evtman = new EventManager() + const current = new Date() + const state = reactive<TState>({ range: !!props.range, - current: new Date(), + current, + next: new Date(current.getFullYear(), current.getMonth() + 1, 1), show: false, input: props.attachInputDom, st: true -- Gitee From 68191e3d2caf9fc99df7344c17a307acfeb25d3d Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Wed, 11 Aug 2021 08:42:32 +0800 Subject: [PATCH 23/42] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E9=80=89=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calendar/components/panel/index.css | 7 +- .../calendar/components/toolbar/index.css | 4 +- .../datepicker/components/calendar/helper.ts | 8 +- .../datepicker/components/calendar/index.css | 38 +++++++++ .../datepicker/components/calendar/index.tsx | 82 +++++++++++++++++-- 5 files changed, 126 insertions(+), 13 deletions(-) diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/calendar/components/panel/index.css index 02c44b35..632c2823 100644 --- a/devui/datepicker/components/calendar/components/panel/index.css +++ b/devui/datepicker/components/calendar/components/panel/index.css @@ -1,8 +1,7 @@ .calendar-panel { - border: 0 solid #dddddd; padding: 5px; - width: 240px; + width: 300px; box-sizing: border-box; } @@ -10,7 +9,7 @@ display: flex; flex-direction: row; justify-content: space-between; - height: 28px; + height: 32px; } .calendar-panel-cell { @@ -53,7 +52,7 @@ .calendar-panel-cell-text { font-weight: normal; font-style: normal; - font-size: 12px; + font-size: 16px; text-align: center; } diff --git a/devui/datepicker/components/calendar/components/toolbar/index.css b/devui/datepicker/components/calendar/components/toolbar/index.css index 5046bb6c..c017c4c2 100644 --- a/devui/datepicker/components/calendar/components/toolbar/index.css +++ b/devui/datepicker/components/calendar/components/toolbar/index.css @@ -1,7 +1,9 @@ .calendar-toolbar { display: flex; justify-content: space-between; - font-size: 12px; + font-size: 16px; + align-items: center; + height: 36px; user-select: none; font-weight: bold; } diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts index eaf9465a..5463fb95 100644 --- a/devui/datepicker/components/calendar/helper.ts +++ b/devui/datepicker/components/calendar/helper.ts @@ -1,4 +1,4 @@ -import { invokeCallback } from './utils' +import { invokeCallback, subDateDay } from './utils' import { TDateCell, TDatePanelDataProps, TDatePanelProps } from './types' export const getDateKey = (date: Date): string => { @@ -40,8 +40,10 @@ export const trigEvent = (props: TDatePanelProps, day: TDateCell): void => { if (!props.dateStart) { invokeCallback(props.onSelectStart, day.date) } else if (!props.dateEnd) { - invokeCallback(props.onSelectEnd, day.date) - typeof props.onChange === 'function' && props.onChange(props.type, props) + if(subDateDay(props.dateStart, day.date) !== 0) { + invokeCallback(props.onSelectEnd, day.date) + typeof props.onChange === 'function' && props.onChange(props.type, props) + } } else { invokeCallback(props.onReset, day.date) } diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css index 34eb5952..58ad8205 100644 --- a/devui/datepicker/components/calendar/index.css +++ b/devui/datepicker/components/calendar/index.css @@ -4,4 +4,42 @@ flex-direction: row; justify-content: flex-start; align-items: flex-start; + user-select: none; + position: relative; +} + +.calendar-panel-time-head { + background-color: #f1f1f1; + text-align: center; + font-size: 16px; + padding: 8px 0; +} + +.calendar-panel-time-select { + display: flex; + flex-direction: row; + width: 150px; + justify-content: space-around; + height: 220px; +} + +.calendar-panel-time-column { + cursor: default; + overflow: auto; + width: 60px; +} + +.calendar-panel-time-column span { + display: block; + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; + border-radius: 5px; + overflow: hidden; +} + +.calendar-panel-time-column span.timepicker-selected { + background-color: #00aaff; + color: #ffffff; } diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 1da65692..3d2f6795 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,28 +1,100 @@ +import { defineComponent, onMounted, ref } from 'vue' import { TProps } from './types' import CalendarDatePanel from './components/panel' import './index.css' +const TimePicker = defineComponent({ + props: { + time: { type: Date } + }, + setup(props) { + const hour = ref<Element>() + const minute = ref<Element>() + const second = ref<Element>() + const idxes = [0, 0, 0] + onMounted(() => { + if(hour.value) { + hour.value.scrollTop = idxes[0] * 32 + } + if(minute.value) { + minute.value.scrollTop = idxes[1] * 32 + } + if(second.value) { + second.value.scrollTop = idxes[2] * 32 + } + }) + + return () => { + const { time = new Date() } = props || {} + const h = time.getHours(), m = time.getMinutes(), s = time.getSeconds() + console.log(h,m,s) + return ( + <div className="calendar-panel-time"> + <div className="calendar-panel-time-head"> + <span>{`00:00:00`}</span> + </div> + <div className="calendar-panel-time-select"> + <div ref={hour} className="calendar-panel-time-column">{ + Array(24).fill(0).map((_, i) => { + let className = '' + if (h === i) { + className = 'timepicker-selected' + idxes[0] = i + } + return <span className={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + <div ref={minute} className="calendar-panel-time-column">{ + Array(60).fill(0).map((_, i) => { + let className = '' + if (m === i) { + className = 'timepicker-selected' + idxes[1] = i + } + return <span className={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + <div ref={second} className="calendar-panel-time-column">{ + Array(60).fill(0).map((_, i) => { + let className = '' + if (s === i) { + className = 'timepicker-selected' + idxes[2] = i + } + return <span className={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + </div> + </div> + ) + } + } +}) + const Calendar = (props: TProps) => { let { current } = props if (!(current instanceof Date)) { current = new Date } - if(props.type === 'range') { + if (props.type === 'range') { let { next } = props - if(!(next instanceof Date)) { + if (!(next instanceof Date)) { next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } return ( <div className="calendar-container"> - <CalendarDatePanel { ...props } pos={0} current={current} compare={next} /> - <CalendarDatePanel { ...props } pos={1} current={next} compare={current} /> + <CalendarDatePanel {...props} pos={0} current={current} compare={next} /> + <TimePicker time={current} /> + <CalendarDatePanel {...props} pos={1} current={next} compare={current} /> + <TimePicker time={next} /> </div> ) } else { return ( <div className="calendar-container"> - <CalendarDatePanel { ...props } pos={0} current={current} /> + <CalendarDatePanel {...props} pos={0} current={current} /> + <TimePicker time={current} /> </div> ) } -- Gitee From 608f9e96c92e17fac918203b77ba1e7b0d4fed09 Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Wed, 11 Aug 2021 09:39:56 +0800 Subject: [PATCH 24/42] =?UTF-8?q?feat:=201.=20=E5=A2=9E=E5=8A=A0predev?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=9B2.=20className=E6=9B=B4=E6=94=B9=E4=B8=BAclas?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datepicker/components/calendar/index.tsx | 23 +++++++++---------- package.json | 1 + 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 3d2f6795..88303dfa 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -28,41 +28,40 @@ const TimePicker = defineComponent({ return () => { const { time = new Date() } = props || {} const h = time.getHours(), m = time.getMinutes(), s = time.getSeconds() - console.log(h,m,s) return ( - <div className="calendar-panel-time"> - <div className="calendar-panel-time-head"> + <div class="calendar-panel-time"> + <div class="calendar-panel-time-head"> <span>{`00:00:00`}</span> </div> - <div className="calendar-panel-time-select"> - <div ref={hour} className="calendar-panel-time-column">{ + <div class="calendar-panel-time-select"> + <div ref={hour} class="calendar-panel-time-column">{ Array(24).fill(0).map((_, i) => { let className = '' if (h === i) { className = 'timepicker-selected' idxes[0] = i } - return <span className={className}>{(i + '').padStart(2, '0')}</span> + return <span class={className}>{(i + '').padStart(2, '0')}</span> }) }</div> - <div ref={minute} className="calendar-panel-time-column">{ + <div ref={minute} class="calendar-panel-time-column">{ Array(60).fill(0).map((_, i) => { let className = '' if (m === i) { className = 'timepicker-selected' idxes[1] = i } - return <span className={className}>{(i + '').padStart(2, '0')}</span> + return <span class={className}>{(i + '').padStart(2, '0')}</span> }) }</div> - <div ref={second} className="calendar-panel-time-column">{ + <div ref={second} class="calendar-panel-time-column">{ Array(60).fill(0).map((_, i) => { let className = '' if (s === i) { className = 'timepicker-selected' idxes[2] = i } - return <span className={className}>{(i + '').padStart(2, '0')}</span> + return <span class={className}>{(i + '').padStart(2, '0')}</span> }) }</div> </div> @@ -83,7 +82,7 @@ const Calendar = (props: TProps) => { next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } return ( - <div className="calendar-container"> + <div class="calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} compare={next} /> <TimePicker time={current} /> <CalendarDatePanel {...props} pos={1} current={next} compare={current} /> @@ -92,7 +91,7 @@ const Calendar = (props: TProps) => { ) } else { return ( - <div className="calendar-container"> + <div class="calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} /> <TimePicker time={current} /> </div> diff --git a/package.json b/package.json index 39442595..d43116c8 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "module": "vue-devui.es.js", "style": "style.css", "scripts": { + "predev": "npm run generate:devui", "dev": "vitepress dev sites", "build": "vitepress build sites", "serve": "vitepress serve sites", -- Gitee From 6f7a12ab525f35555919931037216f8705c06a2a Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Wed, 11 Aug 2021 10:33:34 +0800 Subject: [PATCH 25/42] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96generate-devui.?= =?UTF-8?q?js=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=96=87=E6=A1=A3=E6=BC=94?= =?UTF-8?q?=E7=A4=BA=E7=BB=84=E4=BB=B6=E6=A4=8D=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate-devui.js | 7 +++++++ scripts/get-doc-demos.js | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 scripts/get-doc-demos.js diff --git a/scripts/generate-devui.js b/scripts/generate-devui.js index 34976e1a..b180ce90 100644 --- a/scripts/generate-devui.js +++ b/scripts/generate-devui.js @@ -9,6 +9,8 @@ console.log('config:', config); let importStr = `import { App } from 'vue';\n\n`; const components = []; +const getDocDemos = require('./get-doc-demos') + config['/'].forEach(({ text: ctext, children }) => { if (ctext !== '快速开始') { importStr += `// ${ctext}\n`; @@ -20,6 +22,11 @@ config['/'].forEach(({ text: ctext, children }) => { const filename = linkItem[1]; importStr += `import ${name} from './${filename}';\n`; components.push(name); + + getDocDemos(filename).forEach(({ name, from }) => { + importStr += `import ${name} from './${from}';\n`; + components.push(name); + }) }) importStr += `\n`; } diff --git a/scripts/get-doc-demos.js b/scripts/get-doc-demos.js new file mode 100644 index 00000000..deac9748 --- /dev/null +++ b/scripts/get-doc-demos.js @@ -0,0 +1,44 @@ +const path = require('path'); +const fs = require('fs'); + +const UI_BASE_DIR = path.resolve(__dirname, '../devui') + +/** + * 读取UI组件库中的`doc-demo`目录,收集供文档使用的`demo`组件。 + * - mrundef - 210811 + * @param {string} compName 组件名称 + * @param {string} docDemoDir demo组件存放目录名称,默认`doc-demo` + * @returns + */ +const getDocDemos = (compName, docDemoDir = 'doc-demo') => { + const res = [] + /** 组装`doc-demo`路径 */ + const docDemoPath = path.join(UI_BASE_DIR, compName, docDemoDir) + /** 路径存在 且是目录 */ + if (fs.existsSync(docDemoPath) && fs.statSync(docDemoPath).isDirectory()) { + /** 获取有效组件文件 */ + const names = fs.readdirSync(docDemoPath, 'utf-8') + // 过滤隐藏文件名 + .filter(n => !/^\./.test(n)) + // 仅保留文件 + .filter(n => fs.statSync(path.join(docDemoPath, n)).isFile()) + // 过滤文件类型 js/jsx/ts/tsx/vue + .filter(n => /\.(([jt]sx?|vue)?)$/.test(n)) + names.forEach(name => { + /** 去掉扩展名 */ + const n = name.replace(/\.[^\.\\\/]+$/, '') + /** 组装引入命名名称 */ + const k = `${compName}_${n}` + /** 确保没有重复引用 */ + if (!res.some(({ name }) => name === k)) { + res.push({ + from: `${compName}/doc-demo/${n}`, + name: k + }) + } + }) + } + return res +} + +module.exports = getDocDemos \ No newline at end of file -- Gitee From e1a0d5c917b41db5f1c630f65581caeab43ef77a Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Wed, 11 Aug 2021 10:58:05 +0800 Subject: [PATCH 26/42] =?UTF-8?q?feat:=20datepicker=20demo=E5=92=8C?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datepicker/components/calendar/index.tsx | 7 +- devui/datepicker/components/calendar/types.ts | 1 + devui/datepicker/datepicker-old.tsx | 238 ------------------ devui/datepicker/datepicker.tsx | 4 +- devui/datepicker/doc-demo/demo1.tsx | 30 +++ sites/components/datepicker/index.md | 11 +- 6 files changed, 40 insertions(+), 251 deletions(-) delete mode 100644 devui/datepicker/datepicker-old.tsx create mode 100644 devui/datepicker/doc-demo/demo1.tsx diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 88303dfa..a30c7d75 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -72,6 +72,7 @@ const TimePicker = defineComponent({ }) const Calendar = (props: TProps) => { + const { showTime = false } = props let { current } = props if (!(current instanceof Date)) { current = new Date @@ -84,16 +85,16 @@ const Calendar = (props: TProps) => { return ( <div class="calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} compare={next} /> - <TimePicker time={current} /> + { showTime ? <TimePicker time={current} /> : null } <CalendarDatePanel {...props} pos={1} current={next} compare={current} /> - <TimePicker time={next} /> + { showTime ? <TimePicker time={next} /> : null } </div> ) } else { return ( <div class="calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} /> - <TimePicker time={current} /> + { showTime ? <TimePicker time={current} /> : null } </div> ) } diff --git a/devui/datepicker/components/calendar/types.ts b/devui/datepicker/components/calendar/types.ts index 9b20851d..32d3ae4a 100644 --- a/devui/datepicker/components/calendar/types.ts +++ b/devui/datepicker/components/calendar/types.ts @@ -7,6 +7,7 @@ export type TDateConfig = { type?: TDatePanelType mode?: TDatePanelMode current: Date + showTime: boolean } export type TDateSelectingBase = { diff --git a/devui/datepicker/datepicker-old.tsx b/devui/datepicker/datepicker-old.tsx deleted file mode 100644 index 7b7146da..00000000 --- a/devui/datepicker/datepicker-old.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' -import Input from './components/input' -import PopPanel from './components/pop-panel' -import Calendar from './components/calendar' -import { formatDate, formatRange } from './utils' - -import './datepicker.css' - -const isIn = (start: Node | null, target: Node | null) => { - if(!target) { - return false - } - while(start) { - if(start === target) { - return true - } - start = start.parentNode - } - return false -} - -const factoryAutoClosePanel = (cont: Element, cb: () => void) => (e: MouseEvent) => { - const { target } = e - if(isIn(target as Node, cont)) { - return - } - cb() -} - -const attachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { - el.addEventListener(name, cb, capture) - return { el, name, cb, capture } -} - -const detachEvent = (el: Node | Window, name: string, cb: (e?: any) => any, capture: boolean) => { - el.removeEventListener(name, cb, capture) -} - -const getHostRange = (host: Element): { - left: number - right: number - top: number - bottom: number - width: number - height: number -} => { - const { left, top, width, height } = host.getBoundingClientRect() - const right = window.innerWidth - left - width - const bottom = window.innerHeight - top - height - - // console.log(left, right, top, bottom) - return { left, right, top, bottom, width, height } -} - -export default defineComponent({ - name: 'DDatepicker', - props: { - autoComplete: { type: Boolean, default: false }, - onDateChange: { type: Function }, - range: { type: Boolean, default: false }, - format: { type: String, default: 'y/MM/dd' }, - rangeSpliter: { type: String, default: '-' } - }, - setup(props, ctx) { - const container = ref<Element>() - const popCont = ref<Element>() - const events: { el: Node | Window; cb: (e: any) => void; name: string; capture: boolean; }[] = [] - - const inputState = reactive<{ - showPanel: boolean - panelXPos: 'left' | 'right' - panelYPos: 'top' | 'bottom' - pointX: string - pointY: string - value: string - }>({ - showPanel: false, - panelXPos: 'left', - panelYPos: 'top', - pointX: '0px', - pointY: '0px', - value: '' - }) - - const dateState = reactive<{ - range: boolean - dateCurrent?: Date - dateNext?: Date - dateStart?: Date - dateEnd?: Date - dateHover?: Date - }>({ - range: false, - dateCurrent: new Date(), - dateNext: new Date(2021, 8, 2) - }) - - onMounted(() => { - const { value: cont } = container - if(!cont) { - return - } - - const handleAutoClosePanel = factoryAutoClosePanel(cont, () => { - inputState.showPanel = false - }) - events.push(attachEvent(document, 'click', handleAutoClosePanel, false)) - // // 窗口失焦点时隐藏弹窗 - // events.push(attachEvent(window, 'blur', () => { state.showPanel = false }, false)) - }) - - onUnmounted(() => { - events.forEach(({ el, cb, name, capture }) => detachEvent(el, name, cb, capture)) - events.splice(0, events.length) - }) - - const handleActive = (e: Element) => { - if(inputState.showPanel) { - return - } - const range = getHostRange(e) - if(range.left > range.right) { - inputState.panelXPos = 'right' - inputState.pointX = `${range.width}px` - } else { - inputState.panelXPos = 'left' - inputState.pointX = '0px' - } - - if(range.top > range.bottom) { - inputState.panelYPos = 'bottom' - inputState.pointY = '0px' - } else { - inputState.panelYPos = 'top' - inputState.pointY = `${range.height}px` - } - inputState.showPanel = true - } - - const handleSwitch = (index: number, pos: number, date: Date) => { - switch(index) { - case 0: // previous year - const preYear = new Date(date) - preYear.setFullYear(preYear.getFullYear() - 1) - pos === 0 ? (dateState.dateCurrent = preYear) : (dateState.dateNext = preYear) - break - case 1: // previous month - const preMonth = new Date(date) - preMonth.setMonth(preMonth.getMonth() - 1) - pos === 0 ? (dateState.dateCurrent = preMonth) : (dateState.dateNext = preMonth) - break - case 2: // next month - const nextMonth = new Date(date) - nextMonth.setMonth(nextMonth.getMonth() + 1) - pos === 0 ? (dateState.dateCurrent = nextMonth) : (dateState.dateNext = nextMonth) - break - case 3: // next year - const nextYear = new Date(date) - nextYear.setFullYear(nextYear.getFullYear() + 1) - pos === 0 ? (dateState.dateCurrent = nextYear) : (dateState.dateNext = nextYear) - break - } - } - - const setInputValue = () => { - const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} - if(range) { - inputState.value = formatRange(format, - dateState.dateStart, - dateState.dateEnd, - rangeSpliter - ) - } else { - inputState.value = formatDate(format, dateState.dateStart) - } - } - - const handleSelected = (date: Date) => { - dateState.dateStart = date - setInputValue() - if(props.autoComplete) { - inputState.showPanel = false - } - if(typeof props.onDateChange === 'function') { - props.onDateChange(date) - } - } - - const handleSelectEnd = (date: Date) => { - dateState.dateEnd = date - setInputValue() - if(props.autoComplete) { - inputState.showPanel = false - } - if(typeof props.onDateChange === 'function') { - props.onDateChange(date) - } - } - - return () => { - const { format = 'y/MM/dd', rangeSpliter = '-' } = props || {} - const placeholder = props.range ? `${format} ${rangeSpliter} ${format}` : format - return ( - <div ref={container} class="datapicker-container"> - <Input width={140} onActive={handleActive} value={inputState.value} placeholder={placeholder} /> - <div ref={popCont} class="datepicker-pop-container" style={{ left: inputState.pointX, top: inputState.pointY }}> - <PopPanel - show={inputState.showPanel} - xPosition={inputState.panelXPos} - yPosition={inputState.panelYPos} - xOffset={0} - yOffset={0} - ><Calendar - type={props.range ? 'range' : 'select'} - current={dateState.dateCurrent} - next={dateState.dateNext} - dateStart={dateState.dateStart} - dateEnd={dateState.dateEnd} - dateHover={dateState.dateHover} - onReset={(date: Date) => { - dateState.dateEnd = dateState.dateHover = undefined - dateState.dateStart = date - }} - onSelected={handleSelected} - onSelectStart={(date: Date) => dateState.dateStart = date} - onSelectEnd={handleSelectEnd} - onSelecting={(date: Date) => dateState.dateHover = date} - onPreviousYear={(date: Date, pos: number) => handleSwitch(0, pos, date)} - onPreviousMonth={(date: Date, pos: number) => handleSwitch(1, pos, date)} - onNextMonth={(date: Date, pos: number) => handleSwitch(2, pos, date)} - onNextYear={(date: Date, pos: number) => handleSwitch(3, pos, date)} - /></PopPanel> - </div> - </div> - ) - } - } -}) \ No newline at end of file diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 7590232a..38a9edb8 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -97,6 +97,7 @@ export default defineComponent({ selectedDateChange: { type: Function }, autoClose: { type: Boolean, default: false }, range: { type: Boolean, default: false }, + showTime: { type: Boolean, default: false }, format: { type: String, default: 'y/MM/dd' }, rangeSpliter: { type: String, default: '-' }, attachInputDom: { type: String }, @@ -210,7 +211,7 @@ export default defineComponent({ return () => { handlePosition() return ( - <div className={state.st ? `` : `datepicker-global-viewport`}> + <div class={state.st ? `` : `datepicker-global-viewport`}> <div ref={container} class="datepicker-container" @@ -220,6 +221,7 @@ export default defineComponent({ > <Calendar type={props.range ? 'range' : 'select'} + showTime={props.showTime} current={state.current} next={state.next} dateStart={state.start} diff --git a/devui/datepicker/doc-demo/demo1.tsx b/devui/datepicker/doc-demo/demo1.tsx new file mode 100644 index 00000000..1cd5a635 --- /dev/null +++ b/devui/datepicker/doc-demo/demo1.tsx @@ -0,0 +1,30 @@ +import { defineComponent, reactive } from 'vue' +import DatePicker from '..' + +export default defineComponent({ + name: 'DDatepickerDemo1', + setup() { + const state = reactive<{ + range: boolean + showTime: boolean + }>({ + range: false, + showTime: false, + }) + return () => { + return (<div> + <div style="line-height: 32px;font-size:13px;user-select:none"> + <label style="margin-right:5px;border:1px solid #aaa;padding:5px 10px;border-radius:5px;cursor:pointer;"> + <span>区域选择</span> + <input style="margin:0px;transform:translateX(3px) translateY(2px);" type="checkbox" onChange={() => state.range = !state.range} checked={state.range} /> + </label> + <label style="margin-right:5px;border:1px solid #aaa;padding:5px 10px;border-radius:5px;cursor:pointer;"> + <span>显示时间</span> + <input style="margin:0px;transform:translateX(3px) translateY(2px);" type="checkbox" onChange={() => state.showTime = !state.showTime} checked={state.showTime} /> + </label> + </div> + <DatePicker range={state.range} showTime={state.showTime} /> + </div>) + } + } +}) diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index 6e91d2d5..4f462751 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -6,17 +6,10 @@ ```jsx // 默认 range=false -<d-datepicker /> +<d-datepicker range={state.range} showTime={state.showTime} /> ``` -<d-datepicker /> - -```jsx -// 日期区间选择 range=true -<d-datepicker range /> -``` - -<d-datepicker range /> +<d-datepicker-demo-1 /> ### 绑定原生`<input>` -- Gitee From ae7b56543b2ff9ac133dae1e46a35c5a6e4be93c Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Wed, 11 Aug 2021 12:08:55 +0800 Subject: [PATCH 27/42] =?UTF-8?q?feat:=20DatePicker=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9B=AE=E5=BD=95=E6=89=81=E5=B9=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui-cli/index.js | 0 .../datepicker/components/calendar/index.tsx | 4 +- .../components/{calendar => }/helper.ts | 0 .../toolbar/icons.tsx => icon/index.tsx} | 6 +- devui/datepicker/components/input/index.css | 28 ------ devui/datepicker/components/input/index.tsx | 33 ------- .../{calendar/components => }/panel/index.css | 0 .../{calendar/components => }/panel/index.tsx | 20 ++-- .../datepicker/components/pop-panel/index.css | 6 -- .../datepicker/components/pop-panel/index.tsx | 41 -------- .../index.css} | 0 .../index.tsx} | 6 +- .../components => }/toolbar/index.css | 0 .../components => }/toolbar/index.tsx | 8 +- .../components/{calendar => }/types.ts | 8 +- .../components/{calendar => }/utils.ts | 0 devui/datepicker/demo/datepicker-demo.tsx | 95 +------------------ 17 files changed, 28 insertions(+), 227 deletions(-) mode change 100644 => 100755 devui-cli/index.js rename devui/datepicker/components/{calendar => }/helper.ts (100%) rename devui/datepicker/components/{calendar/components/toolbar/icons.tsx => icon/index.tsx} (88%) delete mode 100644 devui/datepicker/components/input/index.css delete mode 100644 devui/datepicker/components/input/index.tsx rename devui/datepicker/components/{calendar/components => }/panel/index.css (100%) rename devui/datepicker/components/{calendar/components => }/panel/index.tsx (59%) delete mode 100644 devui/datepicker/components/pop-panel/index.css delete mode 100644 devui/datepicker/components/pop-panel/index.tsx rename devui/datepicker/components/{calendar/components/toolbar/toolbar-item.css => toolbar-item/index.css} (100%) rename devui/datepicker/components/{calendar/components/toolbar/toolbar-item.tsx => toolbar-item/index.tsx} (90%) rename devui/datepicker/components/{calendar/components => }/toolbar/index.css (100%) rename devui/datepicker/components/{calendar/components => }/toolbar/index.tsx (85%) rename devui/datepicker/components/{calendar => }/types.ts (87%) rename devui/datepicker/components/{calendar => }/utils.ts (100%) diff --git a/devui-cli/index.js b/devui-cli/index.js old mode 100644 new mode 100755 diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index a30c7d75..6b7db37f 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,6 +1,6 @@ import { defineComponent, onMounted, ref } from 'vue' -import { TProps } from './types' -import CalendarDatePanel from './components/panel' +import { TProps } from '../types' +import CalendarDatePanel from '../panel' import './index.css' diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/helper.ts similarity index 100% rename from devui/datepicker/components/calendar/helper.ts rename to devui/datepicker/components/helper.ts diff --git a/devui/datepicker/components/calendar/components/toolbar/icons.tsx b/devui/datepicker/components/icon/index.tsx similarity index 88% rename from devui/datepicker/components/calendar/components/toolbar/icons.tsx rename to devui/datepicker/components/icon/index.tsx index c6740da1..01d9a31a 100644 --- a/devui/datepicker/components/calendar/components/toolbar/icons.tsx +++ b/devui/datepicker/components/icon/index.tsx @@ -1,8 +1,4 @@ -export type TIconSvgProps = { - color?: string - rotate?: number -} -export type TIconSvg = (props: TIconSvgProps) => any +import { TIconSvgProps } from '../types' export const Year = (props: TIconSvgProps) => { const { color = '#585d6b', rotate = 0 } = props diff --git a/devui/datepicker/components/input/index.css b/devui/datepicker/components/input/index.css deleted file mode 100644 index 0707f8c3..00000000 --- a/devui/datepicker/components/input/index.css +++ /dev/null @@ -1,28 +0,0 @@ -.datapicker-input-container { - position: relative; -} - -.datapicker-input-border { - border: 1px solid #526ecc; - padding: 0 5px; - border-radius: 4px; - box-sizing: border-box; - height: 28px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; -} - -.datapicker-input { - border: none; - outline: none; - position: relative; - padding: 0; - line-height: 16px; - height: 16px; - font-size: 12px; - flex-shrink: 1; - flex-grow: 1; - width: 100%; -} diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx deleted file mode 100644 index 6904d37e..00000000 --- a/devui/datepicker/components/input/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ref } from 'vue' -import Icon from '../../../icon' -import './index.css' - -type TProps = { - width?: number - value?: string - placeholder?: string - onActive?: (el: Element) => void -} - -const DatepickerInput = (props: TProps) => { - const { width = 160, placeholder = '', value = '', onActive } = props - const container = ref<Element>() - const handleClick = () => { - if(container.value && typeof onActive === 'function') { - onActive(container.value) - } - } - return ( - <div - ref={container} - className="datapicker-input-border" - style={{ width: `${width}px` }} - onClick={handleClick} - > - <input className="datapicker-input" type="text" value={value} placeholder={placeholder} /> - <Icon name="calendar" size="16px" /> - </div> - ) -} - -export default DatepickerInput \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/panel/index.css similarity index 100% rename from devui/datepicker/components/calendar/components/panel/index.css rename to devui/datepicker/components/panel/index.css diff --git a/devui/datepicker/components/calendar/components/panel/index.tsx b/devui/datepicker/components/panel/index.tsx similarity index 59% rename from devui/datepicker/components/calendar/components/panel/index.tsx rename to devui/datepicker/components/panel/index.tsx index 84892677..699afb3f 100644 --- a/devui/datepicker/components/calendar/components/panel/index.tsx +++ b/devui/datepicker/components/panel/index.tsx @@ -1,12 +1,12 @@ -import { TDatePanelProps } from '../../types' -import { getMonthWeeklyDays, WEEK_DAYS } from '../../utils' -import { handleDateEnter, cellClassName, trigEvent } from '../../helper' +import { TDatePanelProps } from '../types' +import { getMonthWeeklyDays, WEEK_DAYS } from '../utils' +import { handleDateEnter, cellClassName, trigEvent } from '../helper' import Toolbar from '../toolbar' import './index.css' const CalendarDatePanel = (props: TDatePanelProps) => { return ( - <div className="calendar-panel"> + <div class="calendar-panel"> <Toolbar current={props.current} compare={props.compare} @@ -17,18 +17,18 @@ const CalendarDatePanel = (props: TDatePanelProps) => { onNextMonth={props.onNextMonth} onNextYear={props.onNextYear} /> - <ol className="calendar-panel-header calendar-panel-row">{ - WEEK_DAYS.map(day => <li className="calendar-panel-cell"><b className="calendar-head-cell-text">{day}</b></li>) + <ol class="calendar-panel-header calendar-panel-row">{ + WEEK_DAYS.map(day => <li class="calendar-panel-cell"><b class="calendar-head-cell-text">{day}</b></li>) }</ol> - <ul className="calendar-panel-body">{ - getMonthWeeklyDays(props.current).map(row => <li className="calendar-panel-row">{ + <ul class="calendar-panel-body">{ + getMonthWeeklyDays(props.current).map(row => <li class="calendar-panel-row">{ row.map(day => { return ( <span - className={cellClassName(props as TDatePanelProps, day)} + class={cellClassName(props as TDatePanelProps, day)} onClick={() => trigEvent(props as TDatePanelProps, day)} onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} - ><i className="calendar-panel-cell-text">{day.date.getDate()}</i></span> + ><i class="calendar-panel-cell-text">{day.date.getDate()}</i></span> ) }) }</li>) diff --git a/devui/datepicker/components/pop-panel/index.css b/devui/datepicker/components/pop-panel/index.css deleted file mode 100644 index ba794c3e..00000000 --- a/devui/datepicker/components/pop-panel/index.css +++ /dev/null @@ -1,6 +0,0 @@ -.datapicker-pop-panel { - display: block; - position: absolute; - background-color: #ffffff; - z-index: 99; -} diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx deleted file mode 100644 index 022659da..00000000 --- a/devui/datepicker/components/pop-panel/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { defineComponent, ref, renderSlot, SetupContext } from 'vue' - -import './index.css' - -type TProps = { - show?: boolean - background?: string - xPosition?: 'left' | 'right' - yPosition?: 'top' | 'bottom' - xOffset?: number - yOffset?: number - children?: any -} - -const PopPanel = (props: TProps, ctx: SetupContext) => { - const container = ref<Element>() - if (!props.show) { - return null - } - const { - xPosition = 'left', yPosition = 'top', - xOffset = 0, yOffset = 0, - } = props - - const left = xPosition === 'left' ? `${xOffset}px` : 'auto' - const right = xPosition === 'right' ? `${xOffset}px` : 'auto' - const top = yPosition === 'top' ? `${yOffset}px` : 'auto' - const bottom = yPosition === 'bottom' ? `${yOffset}px` : 'auto' - - const children: any = renderSlot(ctx.slots, 'default') - - return ( - <div - ref={container} - className="datapicker-pop-panel" - style={{ left, right, top, bottom }} - >{children}</div> - ) -} - -export default PopPanel diff --git a/devui/datepicker/components/calendar/components/toolbar/toolbar-item.css b/devui/datepicker/components/toolbar-item/index.css similarity index 100% rename from devui/datepicker/components/calendar/components/toolbar/toolbar-item.css rename to devui/datepicker/components/toolbar-item/index.css diff --git a/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx b/devui/datepicker/components/toolbar-item/index.tsx similarity index 90% rename from devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx rename to devui/datepicker/components/toolbar-item/index.tsx index 5aae2be1..6094106c 100644 --- a/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx +++ b/devui/datepicker/components/toolbar-item/index.tsx @@ -1,6 +1,6 @@ -import { invokeCallback } from '../../utils' -import { TIconSvg } from './icons' -import './toolbar-item.css' +import { invokeCallback } from '../utils' +import { TIconSvg } from '../types' +import './index.css' export type TCalendarToolbarItemProps = { disabled?: boolean diff --git a/devui/datepicker/components/calendar/components/toolbar/index.css b/devui/datepicker/components/toolbar/index.css similarity index 100% rename from devui/datepicker/components/calendar/components/toolbar/index.css rename to devui/datepicker/components/toolbar/index.css diff --git a/devui/datepicker/components/calendar/components/toolbar/index.tsx b/devui/datepicker/components/toolbar/index.tsx similarity index 85% rename from devui/datepicker/components/calendar/components/toolbar/index.tsx rename to devui/datepicker/components/toolbar/index.tsx index 8e8124e0..4e9d593e 100644 --- a/devui/datepicker/components/calendar/components/toolbar/index.tsx +++ b/devui/datepicker/components/toolbar/index.tsx @@ -1,7 +1,7 @@ -import { compareDate } from '../../utils' -import { Year, Month } from './icons' -import { TDateToolbarProps } from '../../types' -import Item, { CalendarToolbarTitle as Title } from './toolbar-item' +import { compareDate } from '../utils' +import { Year, Month } from '../icon' +import { TDateToolbarProps } from '../types' +import Item, { CalendarToolbarTitle as Title } from '../toolbar-item' import './index.css' const CalendarToolbar = (props: TDateToolbarProps) => { diff --git a/devui/datepicker/components/calendar/types.ts b/devui/datepicker/components/types.ts similarity index 87% rename from devui/datepicker/components/calendar/types.ts rename to devui/datepicker/components/types.ts index 32d3ae4a..db472307 100644 --- a/devui/datepicker/components/calendar/types.ts +++ b/devui/datepicker/components/types.ts @@ -49,4 +49,10 @@ export type TProps = ({ } | { type: 'range' next: Date -}) & TDateConfig & TDateSelectingBase & TDatePanelEventProps \ No newline at end of file +}) & TDateConfig & TDateSelectingBase & TDatePanelEventProps + +export type TIconSvgProps = { + color?: string + rotate?: number +} +export type TIconSvg = (props: TIconSvgProps) => any \ No newline at end of file diff --git a/devui/datepicker/components/calendar/utils.ts b/devui/datepicker/components/utils.ts similarity index 100% rename from devui/datepicker/components/calendar/utils.ts rename to devui/datepicker/components/utils.ts diff --git a/devui/datepicker/demo/datepicker-demo.tsx b/devui/datepicker/demo/datepicker-demo.tsx index 431cfb7c..ff2f19ba 100644 --- a/devui/datepicker/demo/datepicker-demo.tsx +++ b/devui/datepicker/demo/datepicker-demo.tsx @@ -1,5 +1,4 @@ import { defineComponent } from 'vue' -import DatePicker from '../datepicker' export default defineComponent({ name: 'DDatepickerDemo', @@ -9,99 +8,7 @@ export default defineComponent({ return () => { return ( <div> - <DatePicker /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /><br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /><br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <div style="margin-left: 200px;"> - <DatePicker /> - </div> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /><br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /><br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <DatePicker /> + Hello DatePicker </div> ) } -- Gitee From a43c441eb5b2314640f2a8d3bfcd8d90b0e4019e Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Wed, 11 Aug 2021 14:34:48 +0800 Subject: [PATCH 28/42] =?UTF-8?q?feat:=20=E7=BB=84=E4=BB=B6=E6=95=B4?= =?UTF-8?q?=E7=90=86=E4=B8=8Escss=E6=94=B9=E9=80=A0=EF=BC=8C=E6=A0=B7?= =?UTF-8?q?=E5=BC=8Fclass=E5=89=8D=E7=BC=80=E7=BB=9F=E4=B8=80=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0`devui-`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datepicker/components/calendar/index.css | 45 ----------- .../datepicker/components/calendar/index.scss | 8 ++ .../datepicker/components/calendar/index.tsx | 75 +----------------- devui/datepicker/components/helper.ts | 2 +- devui/datepicker/components/panel/index.css | 74 ------------------ devui/datepicker/components/panel/index.scss | 78 +++++++++++++++++++ devui/datepicker/components/panel/index.tsx | 17 ++-- .../components/timepicker/index.scss | 48 ++++++++++++ .../components/timepicker/index.tsx | 77 ++++++++++++++++++ .../components/toolbar-item/index.css | 24 ------ .../components/toolbar-item/index.tsx | 42 ---------- devui/datepicker/components/toolbar/index.css | 19 ----- .../datepicker/components/toolbar/index.scss | 45 +++++++++++ devui/datepicker/components/toolbar/index.tsx | 39 ++++++++-- .../{icon/index.tsx => toolbar/svg-icon.tsx} | 0 devui/datepicker/components/types.ts | 11 ++- .../{datepicker.css => datepicker.scss} | 20 ++++- devui/datepicker/datepicker.tsx | 6 +- 18 files changed, 332 insertions(+), 298 deletions(-) delete mode 100644 devui/datepicker/components/calendar/index.css create mode 100644 devui/datepicker/components/calendar/index.scss delete mode 100644 devui/datepicker/components/panel/index.css create mode 100644 devui/datepicker/components/panel/index.scss create mode 100644 devui/datepicker/components/timepicker/index.scss create mode 100644 devui/datepicker/components/timepicker/index.tsx delete mode 100644 devui/datepicker/components/toolbar-item/index.css delete mode 100644 devui/datepicker/components/toolbar-item/index.tsx delete mode 100644 devui/datepicker/components/toolbar/index.css create mode 100644 devui/datepicker/components/toolbar/index.scss rename devui/datepicker/components/{icon/index.tsx => toolbar/svg-icon.tsx} (100%) rename devui/datepicker/{datepicker.css => datepicker.scss} (33%) diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css deleted file mode 100644 index 58ad8205..00000000 --- a/devui/datepicker/components/calendar/index.css +++ /dev/null @@ -1,45 +0,0 @@ -.calendar-container { - border: 0 solid #aaaaaa; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: flex-start; - user-select: none; - position: relative; -} - -.calendar-panel-time-head { - background-color: #f1f1f1; - text-align: center; - font-size: 16px; - padding: 8px 0; -} - -.calendar-panel-time-select { - display: flex; - flex-direction: row; - width: 150px; - justify-content: space-around; - height: 220px; -} - -.calendar-panel-time-column { - cursor: default; - overflow: auto; - width: 60px; -} - -.calendar-panel-time-column span { - display: block; - width: 32px; - height: 32px; - line-height: 32px; - text-align: center; - border-radius: 5px; - overflow: hidden; -} - -.calendar-panel-time-column span.timepicker-selected { - background-color: #00aaff; - color: #ffffff; -} diff --git a/devui/datepicker/components/calendar/index.scss b/devui/datepicker/components/calendar/index.scss new file mode 100644 index 00000000..72fe3cf1 --- /dev/null +++ b/devui/datepicker/components/calendar/index.scss @@ -0,0 +1,8 @@ +.devui-calendar-container { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + user-select: none; + position: relative; +} diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index 6b7db37f..e2e36828 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -1,75 +1,8 @@ -import { defineComponent, onMounted, ref } from 'vue' import { TProps } from '../types' import CalendarDatePanel from '../panel' +import TimePicker from '../timepicker' -import './index.css' - -const TimePicker = defineComponent({ - props: { - time: { type: Date } - }, - setup(props) { - const hour = ref<Element>() - const minute = ref<Element>() - const second = ref<Element>() - const idxes = [0, 0, 0] - onMounted(() => { - if(hour.value) { - hour.value.scrollTop = idxes[0] * 32 - } - if(minute.value) { - minute.value.scrollTop = idxes[1] * 32 - } - if(second.value) { - second.value.scrollTop = idxes[2] * 32 - } - }) - - return () => { - const { time = new Date() } = props || {} - const h = time.getHours(), m = time.getMinutes(), s = time.getSeconds() - return ( - <div class="calendar-panel-time"> - <div class="calendar-panel-time-head"> - <span>{`00:00:00`}</span> - </div> - <div class="calendar-panel-time-select"> - <div ref={hour} class="calendar-panel-time-column">{ - Array(24).fill(0).map((_, i) => { - let className = '' - if (h === i) { - className = 'timepicker-selected' - idxes[0] = i - } - return <span class={className}>{(i + '').padStart(2, '0')}</span> - }) - }</div> - <div ref={minute} class="calendar-panel-time-column">{ - Array(60).fill(0).map((_, i) => { - let className = '' - if (m === i) { - className = 'timepicker-selected' - idxes[1] = i - } - return <span class={className}>{(i + '').padStart(2, '0')}</span> - }) - }</div> - <div ref={second} class="calendar-panel-time-column">{ - Array(60).fill(0).map((_, i) => { - let className = '' - if (s === i) { - className = 'timepicker-selected' - idxes[2] = i - } - return <span class={className}>{(i + '').padStart(2, '0')}</span> - }) - }</div> - </div> - </div> - ) - } - } -}) +import './index.scss' const Calendar = (props: TProps) => { const { showTime = false } = props @@ -83,7 +16,7 @@ const Calendar = (props: TProps) => { next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } return ( - <div class="calendar-container"> + <div className="devui-calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} compare={next} /> { showTime ? <TimePicker time={current} /> : null } <CalendarDatePanel {...props} pos={1} current={next} compare={current} /> @@ -92,7 +25,7 @@ const Calendar = (props: TProps) => { ) } else { return ( - <div class="calendar-container"> + <div className="devui-calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} /> { showTime ? <TimePicker time={current} /> : null } </div> diff --git a/devui/datepicker/components/helper.ts b/devui/datepicker/components/helper.ts index 5463fb95..5a06ae17 100644 --- a/devui/datepicker/components/helper.ts +++ b/devui/datepicker/components/helper.ts @@ -5,7 +5,7 @@ export const getDateKey = (date: Date): string => { return date.toDateString() } -export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = 'calendar-panel-cell'): string => { +export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = 'cell'): string => { if(day.current !== 0) { return `${base} disabled` } diff --git a/devui/datepicker/components/panel/index.css b/devui/datepicker/components/panel/index.css deleted file mode 100644 index 632c2823..00000000 --- a/devui/datepicker/components/panel/index.css +++ /dev/null @@ -1,74 +0,0 @@ - -.calendar-panel { - padding: 5px; - width: 300px; - box-sizing: border-box; -} - -.calendar-panel-row { - display: flex; - flex-direction: row; - justify-content: space-between; - height: 32px; -} - -.calendar-panel-cell { - width: 100%; - text-align: center; - flex-grow: 1; - flex-shrink: 1; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} - -.calendar-panel-body .calendar-panel-cell:hover { - background-color: #eeeeee; - color: #000000; -} - -.calendar-panel-cell.selected { - background-color: #0066cc; - color: #ffffff; - border-radius: 6px; -} - -.calendar-panel-cell.selected:hover { - background-color: #0066cc; - color: #ffffff; -} - -.calendar-panel-cell.innerday { - background-color: #f1f1f1; -} - -.calendar-panel-cell.disabled { - background-color: #ffffff; - color: #bbbbbb; -} - -.calendar-head-cell-text, -.calendar-panel-cell-text { - font-weight: normal; - font-style: normal; - font-size: 16px; - text-align: center; -} - -.calendar-panel-header { - height: 32px; - cursor: default; - padding: 0; - margin: 0; - list-style: none; -} - -.calendar-panel-body { - display: flex; - flex-direction: column; - cursor: pointer; - padding: 0; - margin: 0; - list-style: none; -} diff --git a/devui/datepicker/components/panel/index.scss b/devui/datepicker/components/panel/index.scss new file mode 100644 index 00000000..57ae53ec --- /dev/null +++ b/devui/datepicker/components/panel/index.scss @@ -0,0 +1,78 @@ +@import '../../../style/devui.scss'; + +$panel-width: 230px; +$panel-height: 210px; +$panel-padding: 5px; +$panel-row-height: 24px; +$panel-cell-bg: #ffffff; +$panel-cell-color: #000000; +$panel-cell-active-bg: #0066cc; +$panel-cell-active-color: #f1f1f1; +$panel-cell-active-hover-bg: #0088dd; +$panel-cell-active-hover-color: #ffffff; + +.devui-calendar-panel { + width: $panel-width; + height: $panel-height; + padding: $panel-padding; + box-sizing: border-box; + overflow: hidden; + + .row { + display: flex; + flex-direction: row; + justify-content: space-between; + height: $panel-row-height; + + .cell { + width: 100%; + text-align: center; + flex-grow: 1; + flex-shrink: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + border-radius: $devui-border-radius; + background-color: $panel-cell-bg; + color: $panel-cell-color; + + &:hover { + background-color: $devui-disabled-bg; + } + + &.selected { + background-color: $panel-cell-active-bg; + color: $panel-cell-active-color; + + &:hover { + background-color: $panel-cell-active-hover-bg; + color: $panel-cell-active-hover-color; + } + } + + &.innerday { + background-color: $devui-disabled-bg; + } + + &.disabled { + color: $devui-disabled-text; + } + } + } + + .head { + cursor: default; + padding: 0; + margin: 0; + } + + .body { + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + cursor: pointer; + list-style: none; + } +} diff --git a/devui/datepicker/components/panel/index.tsx b/devui/datepicker/components/panel/index.tsx index 699afb3f..6af7f6bd 100644 --- a/devui/datepicker/components/panel/index.tsx +++ b/devui/datepicker/components/panel/index.tsx @@ -2,33 +2,34 @@ import { TDatePanelProps } from '../types' import { getMonthWeeklyDays, WEEK_DAYS } from '../utils' import { handleDateEnter, cellClassName, trigEvent } from '../helper' import Toolbar from '../toolbar' -import './index.css' +import './index.scss' const CalendarDatePanel = (props: TDatePanelProps) => { return ( - <div class="calendar-panel"> + <div className="devui-calendar-panel"> <Toolbar current={props.current} compare={props.compare} pos={props.pos} type={props.type} + showTime={props.showTime} onPreviousYear={props.onPreviousYear} onPreviousMonth={props.onPreviousMonth} onNextMonth={props.onNextMonth} onNextYear={props.onNextYear} /> - <ol class="calendar-panel-header calendar-panel-row">{ - WEEK_DAYS.map(day => <li class="calendar-panel-cell"><b class="calendar-head-cell-text">{day}</b></li>) + <ol className="head row">{ + WEEK_DAYS.map(day => <li className="cell">{day}</li>) }</ol> - <ul class="calendar-panel-body">{ - getMonthWeeklyDays(props.current).map(row => <li class="calendar-panel-row">{ + <ul className="body">{ + getMonthWeeklyDays(props.current).map(row => <li className="row">{ row.map(day => { return ( <span - class={cellClassName(props as TDatePanelProps, day)} + className={cellClassName(props as TDatePanelProps, day)} onClick={() => trigEvent(props as TDatePanelProps, day)} onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} - ><i class="calendar-panel-cell-text">{day.date.getDate()}</i></span> + >{day.date.getDate()}</span> ) }) }</li>) diff --git a/devui/datepicker/components/timepicker/index.scss b/devui/datepicker/components/timepicker/index.scss new file mode 100644 index 00000000..a6afcd88 --- /dev/null +++ b/devui/datepicker/components/timepicker/index.scss @@ -0,0 +1,48 @@ +@import '../../../style/devui.scss'; +@import '../panel/index.scss'; + +$calendar-timepicker-width: 100px; +$head-height: 32px; + +.devui-calendar-timepicker { + width: $calendar-timepicker-width; + height: $panel-height; + overflow: hidden; + + .head { + background-color: #f1f1f1; + text-align: center; + height: $head-height; + line-height: $head-height; + } + + .select { + display: flex; + flex-direction: row; + justify-content: space-around; + height: $panel-height - $head-height; + + .column { + cursor: default; + overflow: auto; + flex-grow: 1; + + span { + display: block; + font-size: 11px; + width: 24px; + height: 24px; + line-height: 24px; + text-align: center; + overflow: hidden; + border-radius: 100%; + background-color: #f6f6f6; + + &.selected { + background-color: #00aaff; + color: #ffffff; + } + } + } + } +} diff --git a/devui/datepicker/components/timepicker/index.tsx b/devui/datepicker/components/timepicker/index.tsx new file mode 100644 index 00000000..3b0b90d5 --- /dev/null +++ b/devui/datepicker/components/timepicker/index.tsx @@ -0,0 +1,77 @@ +import { defineComponent, onMounted, ref } from 'vue' +import { TProps } from '../types' +import CalendarDatePanel from '../panel' + +import './index.scss' + +const TimePicker = defineComponent({ + props: { + time: { type: Date } + }, + setup(props) { + const hour = ref<Element>() + const minute = ref<Element>() + const second = ref<Element>() + const idxes = [0, 0, 0] + const size = 24 + onMounted(() => { + if(hour.value) { + // hour.value.scrollTop = idxes[0] * size + hour.value.scrollTop = idxes[0] * size + console.log(idxes[0] * size) + } + if(minute.value) { + minute.value.scrollTop = idxes[1] * size + } + if(second.value) { + second.value.scrollTop = idxes[2] * size + } + }) + + return () => { + const { time = new Date() } = props || {} + const h = time.getHours(), m = time.getMinutes(), s = time.getSeconds() + return ( + <div className="devui-calendar-timepicker"> + <div className="head"> + <span>{`00:00:00`}</span> + </div> + <div className="select"> + <div ref={hour} className="column">{ + Array(24).fill(0).map((_, i) => { + let className = '' + if (h === i) { + className = 'selected' + idxes[0] = i + } + return <span className={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + <div ref={minute} className="column">{ + Array(60).fill(0).map((_, i) => { + let className = '' + if (m === i) { + className = 'selected' + idxes[1] = i + } + return <span className={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + <div ref={second} className="column">{ + Array(60).fill(0).map((_, i) => { + let className = '' + if (s === i) { + className = 'selected' + idxes[2] = i + } + return <span className={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + </div> + </div> + ) + } + } +}) + +export default TimePicker \ No newline at end of file diff --git a/devui/datepicker/components/toolbar-item/index.css b/devui/datepicker/components/toolbar-item/index.css deleted file mode 100644 index e207dcb3..00000000 --- a/devui/datepicker/components/toolbar-item/index.css +++ /dev/null @@ -1,24 +0,0 @@ -.calendar-toolbar-button { - display: block; - width: 28px; - height: 28px; - line-height: 28px; - text-align: center; - border: 1px soild #000000; - flex-shrink: 0; - flex-grow: 0; -} - -.calendar-toolbar-button.title { - flex-grow: 1; - flex-shrink: 1; -} - -.calendar-toolbar-button.disabled { - color: #aaaaaa; - cursor: not-allowed; -} - -.calendar-toolbar-button.disabled:hover { - color: #aaaaaa; -} diff --git a/devui/datepicker/components/toolbar-item/index.tsx b/devui/datepicker/components/toolbar-item/index.tsx deleted file mode 100644 index 6094106c..00000000 --- a/devui/datepicker/components/toolbar-item/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { invokeCallback } from '../utils' -import { TIconSvg } from '../types' -import './index.css' - -export type TCalendarToolbarItemProps = { - disabled?: boolean - rotate?: number - cb?: (...args: any[]) => void - pos: number - date: Date - button: TIconSvg -} - -const CalendarToolbarItem = (props: TCalendarToolbarItemProps) => { - const { - button: Btn, - disabled = false, - rotate = 0, - date, - pos, - cb, - } = props - const color = disabled ? '#cfd0d3' : '#585d6b' - const className = `calendar-toolbar-button ${disabled ? 'disabled' : ''}` - const handleClick = disabled ? undefined : () => invokeCallback(props.cb, date, pos) - return ( - <a className={className} onClick={handleClick}> - <Btn color={color} rotate={rotate} /> - </a> - ) -} - -export const CalendarToolbarTitle = (props: { date: Date; }) => { - const { date } = props - return ( - <a className="calendar-toolbar-button title">{ - `${date.getFullYear()}年${(date.getMonth() + 1 + '').padStart(2, '0')}月` - }</a> - ) -} - -export default CalendarToolbarItem \ No newline at end of file diff --git a/devui/datepicker/components/toolbar/index.css b/devui/datepicker/components/toolbar/index.css deleted file mode 100644 index c017c4c2..00000000 --- a/devui/datepicker/components/toolbar/index.css +++ /dev/null @@ -1,19 +0,0 @@ -.calendar-toolbar { - display: flex; - justify-content: space-between; - font-size: 16px; - align-items: center; - height: 36px; - user-select: none; - font-weight: bold; -} - -.calendar-toolbar a { - color: #000000; - text-decoration: none; - cursor: pointer; -} - -.calendar-toolbar a:hover { - color: #0066cc; -} diff --git a/devui/datepicker/components/toolbar/index.scss b/devui/datepicker/components/toolbar/index.scss new file mode 100644 index 00000000..d73f4afc --- /dev/null +++ b/devui/datepicker/components/toolbar/index.scss @@ -0,0 +1,45 @@ +@import '../../../style/devui.scss'; + +$toolbar-height: 32px; +$cell-size: 28px; + +.devui-calendar-toolbar { + height: $toolbar-height; + font-weight: $font-title-weight; + display: flex; + justify-content: space-between; + align-items: center; + user-select: none; + + a { + width: $cell-size; + height: $cell-size; + line-height: $cell-size; + color: $text-color; + display: block; + text-align: center; + flex-shrink: 1; + flex-grow: 0; + cursor: pointer; + text-decoration: none; + + &:hover { + color: $text-color; + text-decoration: none; + } + + &.disabled { + color: $devui-disabled-text; + cursor: not-allowed; + + &:hover { + color: $devui-disabled-text; + } + } + + &.title { + flex-grow: 1; + flex-shrink: 1; + } + } +} diff --git a/devui/datepicker/components/toolbar/index.tsx b/devui/datepicker/components/toolbar/index.tsx index 4e9d593e..ffc10f99 100644 --- a/devui/datepicker/components/toolbar/index.tsx +++ b/devui/datepicker/components/toolbar/index.tsx @@ -1,8 +1,35 @@ -import { compareDate } from '../utils' -import { Year, Month } from '../icon' -import { TDateToolbarProps } from '../types' -import Item, { CalendarToolbarTitle as Title } from '../toolbar-item' -import './index.css' +import { compareDate, invokeCallback } from '../utils' +import { Year, Month } from './svg-icon' +import { TCalendarToolbarItemProps, TDateToolbarProps } from '../types' +import './index.scss' + +const Item = (props: TCalendarToolbarItemProps) => { + const { + button: Btn, + disabled = false, + rotate = 0, + date, + pos, + cb, + } = props + const color = disabled ? '#cfd0d3' : '#585d6b' + const className = `${disabled ? 'disabled' : ''}` + const handleClick = disabled ? undefined : () => invokeCallback(props.cb, date, pos) + return ( + <a className={className} onClick={handleClick}> + <Btn color={color} rotate={rotate} /> + </a> + ) +} + +export const Title = (props: { date: Date; }) => { + const { date } = props + return ( + <a className="title">{ + `${date.getFullYear()}年${(date.getMonth() + 1 + '').padStart(2, '0')}月` + }</a> + ) +} const CalendarToolbar = (props: TDateToolbarProps) => { const { @@ -25,7 +52,7 @@ const CalendarToolbar = (props: TDateToolbarProps) => { } return ( - <div className="calendar-toolbar"> + <div className="devui-calendar-toolbar"> <Item disabled={dis[0]} date={current} pos={pos} button={Year} cb={onPreviousYear} /> <Item disabled={dis[1]} date={current} pos={pos} button={Month} rotate={-90} cb={onPreviousMonth} /> <Title date={current} /> diff --git a/devui/datepicker/components/icon/index.tsx b/devui/datepicker/components/toolbar/svg-icon.tsx similarity index 100% rename from devui/datepicker/components/icon/index.tsx rename to devui/datepicker/components/toolbar/svg-icon.tsx diff --git a/devui/datepicker/components/types.ts b/devui/datepicker/components/types.ts index db472307..e366e168 100644 --- a/devui/datepicker/components/types.ts +++ b/devui/datepicker/components/types.ts @@ -55,4 +55,13 @@ export type TIconSvgProps = { color?: string rotate?: number } -export type TIconSvg = (props: TIconSvgProps) => any \ No newline at end of file +export type TIconSvg = (props: TIconSvgProps) => any + +export type TCalendarToolbarItemProps = { + disabled?: boolean + rotate?: number + cb?: (...args: any[]) => void + pos: number + date: Date + button: TIconSvg +} \ No newline at end of file diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.scss similarity index 33% rename from devui/datepicker/datepicker.css rename to devui/datepicker/datepicker.scss index 4eb33598..26d48111 100644 --- a/devui/datepicker/datepicker.css +++ b/devui/datepicker/datepicker.scss @@ -1,4 +1,11 @@ -.datepicker-global-viewport { +@import '../style/devui.scss'; + +$cell-font-size: 13px; +$border-width: 1px; +$border-style: solid; +$border-color: #dddddd; + +.devui-datepicker-global-viewport { position: fixed; left: 0; top: 0; @@ -9,11 +16,16 @@ overflow: visible; } -.datepicker-container { +.devui-datepicker-container { margin: 0; padding: 0; position: relative; display: inline-block; - border: 1px solid #cccccc; - background-color: #ffffff; + border-width: $border-width; + border-style: $border-style; + border-color: $border-color; + border-radius: $devui-border-radius-card; + box-shadow: $devui-shadow-length-base $devui-shadow; + background-color: $devui-base-bg; + font-size: $cell-font-size; } diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 38a9edb8..fb124a95 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -5,7 +5,7 @@ import { traceNode, invokeFunction, } from './utils' import Calendar from './components/calendar' -import './datepicker.css' +import './datepicker.scss' type TState = { range?: boolean @@ -211,10 +211,10 @@ export default defineComponent({ return () => { handlePosition() return ( - <div class={state.st ? `` : `datepicker-global-viewport`}> + <div class={state.st ? `` : `devui-datepicker-global-viewport`}> <div ref={container} - class="datepicker-container" + class="devui-datepicker-container" style={{ transform: state.st ? '' : `translateX(${pos.x}) translateY(${pos.y})` }} -- Gitee From 79d4896d64f0870aa00b100a2b15dd1fc58fb2a1 Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Wed, 11 Aug 2021 19:04:46 +0800 Subject: [PATCH 29/42] feat: vertical-slider & new timepicker --- .../components/timepicker/index.scss | 27 +++- .../components/timepicker/index.tsx | 88 +++++-------- .../components/vertical-slider/index.scss | 45 +++++++ .../components/vertical-slider/index.tsx | 120 ++++++++++++++++++ devui/datepicker/doc-demo/demo1.scss | 18 +++ devui/datepicker/doc-demo/demo1.tsx | 13 +- 6 files changed, 249 insertions(+), 62 deletions(-) create mode 100644 devui/datepicker/components/vertical-slider/index.scss create mode 100644 devui/datepicker/components/vertical-slider/index.tsx create mode 100644 devui/datepicker/doc-demo/demo1.scss diff --git a/devui/datepicker/components/timepicker/index.scss b/devui/datepicker/components/timepicker/index.scss index a6afcd88..139b9aaa 100644 --- a/devui/datepicker/components/timepicker/index.scss +++ b/devui/datepicker/components/timepicker/index.scss @@ -8,12 +8,33 @@ $head-height: 32px; width: $calendar-timepicker-width; height: $panel-height; overflow: hidden; + position: relative; .head { - background-color: #f1f1f1; - text-align: center; height: $head-height; line-height: $head-height; + background-color: #f1f1f1; + text-align: center; + display: flex; + justify-content: center; + // position: absolute; + // width: 100%; + // top: 50%; + // margin-top: -($head-height / 2); + + // z-index: 20; + + // .chars { + // width: 38%; + // display: flex; + // justify-content: space-between; + + // span { + // color: #000; + // // font-size: 16px; + // // margin-top: -2px + // } + // } } .select { @@ -21,7 +42,7 @@ $head-height: 32px; flex-direction: row; justify-content: space-around; height: $panel-height - $head-height; - + // height: $panel-height; .column { cursor: default; overflow: auto; diff --git a/devui/datepicker/components/timepicker/index.tsx b/devui/datepicker/components/timepicker/index.tsx index 3b0b90d5..727b06a7 100644 --- a/devui/datepicker/components/timepicker/index.tsx +++ b/devui/datepicker/components/timepicker/index.tsx @@ -1,6 +1,5 @@ -import { defineComponent, onMounted, ref } from 'vue' -import { TProps } from '../types' -import CalendarDatePanel from '../panel' +import { defineComponent, onMounted, ref, reactive } from 'vue' +import VerticalSliderFunction from '../vertical-slider' import './index.scss' @@ -9,64 +8,47 @@ const TimePicker = defineComponent({ time: { type: Date } }, setup(props) { - const hour = ref<Element>() - const minute = ref<Element>() - const second = ref<Element>() - const idxes = [0, 0, 0] - const size = 24 - onMounted(() => { - if(hour.value) { - // hour.value.scrollTop = idxes[0] * size - hour.value.scrollTop = idxes[0] * size - console.log(idxes[0] * size) - } - if(minute.value) { - minute.value.scrollTop = idxes[1] * size - } - if(second.value) { - second.value.scrollTop = idxes[2] * size - } + + const { time = new Date() } = props || {} + const state = reactive({ + hour: time.getHours(), + minute: time.getMinutes(), + second: time.getSeconds() }) + const hours = Array(24).fill(0).map((_, i) => `${i}`.padStart(2, '0')) + const minutes = Array(60).fill(0).map((_, i) => `${i}`.padStart(2, '0')) + return () => { - const { time = new Date() } = props || {} - const h = time.getHours(), m = time.getMinutes(), s = time.getSeconds() return ( <div className="devui-calendar-timepicker"> <div className="head"> - <span>{`00:00:00`}</span> + <div className="chars"> + {/* <span>{`:`}</span> + <span>{`:`}</span> */} + <span> + {state.hour.toString().padStart(2, '0')}: + {state.minute.toString().padStart(2, '0')}: + {state.second.toString().padStart(2, '0')} + </span> + </div> </div> <div className="select"> - <div ref={hour} className="column">{ - Array(24).fill(0).map((_, i) => { - let className = '' - if (h === i) { - className = 'selected' - idxes[0] = i - } - return <span className={className}>{(i + '').padStart(2, '0')}</span> - }) - }</div> - <div ref={minute} className="column">{ - Array(60).fill(0).map((_, i) => { - let className = '' - if (m === i) { - className = 'selected' - idxes[1] = i - } - return <span className={className}>{(i + '').padStart(2, '0')}</span> - }) - }</div> - <div ref={second} className="column">{ - Array(60).fill(0).map((_, i) => { - let className = '' - if (s === i) { - className = 'selected' - idxes[2] = i - } - return <span className={className}>{(i + '').padStart(2, '0')}</span> - }) - }</div> + <VerticalSliderFunction + items={hours} + selectedIndex={state.hour} + onChange={(_: any, idx: number) => state.hour = idx} + /> + <VerticalSliderFunction + items={minutes} + selectedIndex={state.minute} + onChange={(_: any, idx: number) => state.minute = idx} + /> + <VerticalSliderFunction + items={minutes} + selectedIndex={state.second} + onChange={(_: any, idx: number) => state.second = idx} + /> </div> </div> ) diff --git a/devui/datepicker/components/vertical-slider/index.scss b/devui/datepicker/components/vertical-slider/index.scss new file mode 100644 index 00000000..8e2160d2 --- /dev/null +++ b/devui/datepicker/components/vertical-slider/index.scss @@ -0,0 +1,45 @@ +.devui-vertical-slider { + display: flex; + flex-direction: column; + min-width: 24px; + text-align: center; + position: relative; + overflow: hidden; + flex-grow: 1; + + .movable-bar { + position: relative; + overflow: visible; + + .slider-item { + display: block; + flex-grow: 0; + flex-shrink: 0; + box-sizing: border-box; + + // &.selected { + // background-color: #06c; + // color: #fff; + // } + } + } + + .forcus { + position: absolute; + box-sizing: border-box; + width: 100%; + height: 50%; + top: 25%; + } + + .slider-mask { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + background-color: rgba(0, 0, 0, 0); + background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.3), transparent, rgba(0, 0, 0, 0.3)); + z-index: 99; + } +} diff --git a/devui/datepicker/components/vertical-slider/index.tsx b/devui/datepicker/components/vertical-slider/index.tsx new file mode 100644 index 00000000..b374cb8e --- /dev/null +++ b/devui/datepicker/components/vertical-slider/index.tsx @@ -0,0 +1,120 @@ +import { defineComponent, reactive, onMounted, ref } from 'vue' + +import './index.scss' + +const VerticalSlider = defineComponent({ + props: { + size: { type: Number, default: 26 }, + items: { type: Array }, + selectedIndex: { type: Number }, + className: { type: String }, + itemClassNormal: { type: String }, + itemClassSelected: { type: String }, + onChange: { type: Function } + }, + setup(props) { + + const { + items = [0,1,2,3,4,5,6,7,8,9], + selectedIndex = 0, + size = 26, + className = '', + itemClassNormal = '', + itemClassSelected = 'selected', + onChange, + } = props || {} + + let max_y = 0, min_y = 0 + const container = ref<Element>() + const movbar = ref<Element>() + + let pos_start: [number, number] | null = null + let pos_cache: [number, number] | null = null + + const state = reactive<{ + selectedIndex: number + barOpacity: number + x: number + y: number + transition: string + }>({ + selectedIndex, + barOpacity: 0, + x: 0, y: 0, + transition: 'none', + }) + + const handleMouseDown = (e: MouseEvent) => { + e.stopPropagation() + e.preventDefault() + pos_start = [e.clientX, e.clientY] + state.transition = 'none' + } + + const handleMouseMove = (e: MouseEvent) => { + e.stopPropagation() + e.preventDefault() + if(!pos_start || !pos_cache) { + return + } + state.x = pos_cache[0] + e.clientX - pos_start[0] + state.y = Math.min(max_y, Math.max(min_y, pos_cache[1] + e.clientY - pos_start[1])) + state.selectedIndex = (max_y - state.y + size / 2) / size >> 0 + } + const handleMouseUp = (e: MouseEvent) => { + e.stopPropagation() + e.preventDefault() + pos_start = null + state.y = max_y - state.selectedIndex * size; + state.transition = 'transform 0.1s' + pos_cache[0] = state.x + pos_cache[1] = state.y + if(typeof onChange === 'function') { + const idx = state.selectedIndex + const val = items[idx] + onChange(val, idx) + } + } + + onMounted(() => { + const { height: ch } = container.value.getBoundingClientRect() + const { height: mh } = movbar.value.getBoundingClientRect() + max_y = (ch - size) / 2 + min_y = (ch + size) / 2 - mh + pos_cache = [0, max_y - state.selectedIndex * size] + state.x = pos_cache[0] + state.y = pos_cache[1] + state.barOpacity = 1 + state.transition = 'transform 0.1s' + // console.log(ch, mh) + }) + + return () => { + return ( + <div ref={container} className={`devui-vertical-slider ${className}`}> + <div ref={movbar} className="movable-bar" style={{ + opacity: state.barOpacity, + transform: `translateY(${state.y}px)`, + transition: state.transition, + }}> + { + items.map((c, i) => { + const className = i === state.selectedIndex ? itemClassSelected : itemClassNormal + return <span className={`slider-item ${className}`} style={{ height: `${size}px`, lineHeight: `${size}px` }}>{c}</span> + }) + } + </div> + <div + className="slider-mask" + onMousedown={handleMouseDown} + onMousemove={handleMouseMove} + onMouseup={handleMouseUp} + onMouseout={handleMouseUp} + ></div> + </div> + ) + } + } +}) + +export default VerticalSlider \ No newline at end of file diff --git a/devui/datepicker/doc-demo/demo1.scss b/devui/datepicker/doc-demo/demo1.scss new file mode 100644 index 00000000..391679da --- /dev/null +++ b/devui/datepicker/doc-demo/demo1.scss @@ -0,0 +1,18 @@ +.devui-datepicker-demo1 { + line-height: 32px; + font-size: 13px; + user-select: none; + + label { + margin-right: 5px; + border: 1px solid #aaaaaa; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + + input[type='checkbox'] { + margin: 0; + transform: translateX(3px) translateY(2px); + } + } +} diff --git a/devui/datepicker/doc-demo/demo1.tsx b/devui/datepicker/doc-demo/demo1.tsx index 1cd5a635..a48b4ac4 100644 --- a/devui/datepicker/doc-demo/demo1.tsx +++ b/devui/datepicker/doc-demo/demo1.tsx @@ -1,5 +1,6 @@ import { defineComponent, reactive } from 'vue' import DatePicker from '..' +import './demo1.scss' export default defineComponent({ name: 'DDatepickerDemo1', @@ -9,18 +10,18 @@ export default defineComponent({ showTime: boolean }>({ range: false, - showTime: false, + showTime: true, }) return () => { return (<div> - <div style="line-height: 32px;font-size:13px;user-select:none"> - <label style="margin-right:5px;border:1px solid #aaa;padding:5px 10px;border-radius:5px;cursor:pointer;"> + <div className="devui-datepicker-demo1"> + <label> <span>区域选择</span> - <input style="margin:0px;transform:translateX(3px) translateY(2px);" type="checkbox" onChange={() => state.range = !state.range} checked={state.range} /> + <input type="checkbox" onChange={() => state.range = !state.range} checked={state.range} /> </label> - <label style="margin-right:5px;border:1px solid #aaa;padding:5px 10px;border-radius:5px;cursor:pointer;"> + <label> <span>显示时间</span> - <input style="margin:0px;transform:translateX(3px) translateY(2px);" type="checkbox" onChange={() => state.showTime = !state.showTime} checked={state.showTime} /> + <input type="checkbox" onChange={() => state.showTime = !state.showTime} checked={state.showTime} /> </label> </div> <DatePicker range={state.range} showTime={state.showTime} /> -- Gitee From b033a86c94260e307c54367b8cfe07f520a911cb Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Wed, 11 Aug 2021 22:31:37 +0800 Subject: [PATCH 30/42] feat: restore predev --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 809ff374..087fcf98 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "devui-cli": "./devui-cli/index.js" }, "scripts": { - "predev": "npm run generate:devui", "dev": "vitepress dev sites", "build": "vitepress build sites", "serve": "vitepress serve sites", -- Gitee From 8c47fa4ef38778623425324d407ad743e821e9c9 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Wed, 11 Aug 2021 22:57:53 +0800 Subject: [PATCH 31/42] =?UTF-8?q?feat:=20datepicker=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=96=B9=E5=BC=8F=E6=A0=87=E5=87=86=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/doc-demo/demo1.tsx | 2 +- devui/datepicker/index.ts | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/devui/datepicker/doc-demo/demo1.tsx b/devui/datepicker/doc-demo/demo1.tsx index a48b4ac4..2f1c3566 100644 --- a/devui/datepicker/doc-demo/demo1.tsx +++ b/devui/datepicker/doc-demo/demo1.tsx @@ -1,5 +1,5 @@ import { defineComponent, reactive } from 'vue' -import DatePicker from '..' +import DatePicker from '../datepicker' import './demo1.scss' export default defineComponent({ diff --git a/devui/datepicker/index.ts b/devui/datepicker/index.ts index 17134f3e..dd2d0fae 100644 --- a/devui/datepicker/index.ts +++ b/devui/datepicker/index.ts @@ -1,10 +1,13 @@ import { App } from 'vue' import DatePicker from './datepicker' +import DatePickerDemo1 from './doc-demo/demo1' -DatePicker.install = function(Vue: App) { - Vue.component(DatePicker.name, DatePicker) -}; +export { DatePicker, DatePickerDemo1 } -DatePicker.version = '0.0.1' - -export default DatePicker +export default { + install(app: App) { + DatePicker.version = '0.0.1' + app.component(DatePicker.name, DatePicker) + app.component(DatePickerDemo1.name, DatePickerDemo1) + } +} -- Gitee From e35b607af98c249d992393883da6ea64b8f1cde0 Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Thu, 12 Aug 2021 09:32:20 +0800 Subject: [PATCH 32/42] =?UTF-8?q?feat:=20=E5=8E=BB=E6=8E=89demo=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/devui/datepicker/index.ts b/devui/datepicker/index.ts index dd2d0fae..2a7ff4dd 100644 --- a/devui/datepicker/index.ts +++ b/devui/datepicker/index.ts @@ -1,13 +1,11 @@ import { App } from 'vue' import DatePicker from './datepicker' -import DatePickerDemo1 from './doc-demo/demo1' -export { DatePicker, DatePickerDemo1 } +export { DatePicker } export default { install(app: App) { DatePicker.version = '0.0.1' app.component(DatePicker.name, DatePicker) - app.component(DatePickerDemo1.name, DatePickerDemo1) } } -- Gitee From 9df7e04df7e64a2b07e7de2c72d6781de0ae925d Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Thu, 12 Aug 2021 10:12:13 +0800 Subject: [PATCH 33/42] =?UTF-8?q?feat:=20DatePicker=E6=BC=94=E7=A4=BA?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/datepicker/demo}/demo1.scss | 0 .../components/datepicker/demo}/demo1.tsx | 12 +++++++++--- sites/components/datepicker/index.md | 5 +++++ 3 files changed, 14 insertions(+), 3 deletions(-) rename {devui/datepicker/doc-demo => sites/components/datepicker/demo}/demo1.scss (100%) rename {devui/datepicker/doc-demo => sites/components/datepicker/demo}/demo1.tsx (79%) diff --git a/devui/datepicker/doc-demo/demo1.scss b/sites/components/datepicker/demo/demo1.scss similarity index 100% rename from devui/datepicker/doc-demo/demo1.scss rename to sites/components/datepicker/demo/demo1.scss diff --git a/devui/datepicker/doc-demo/demo1.tsx b/sites/components/datepicker/demo/demo1.tsx similarity index 79% rename from devui/datepicker/doc-demo/demo1.tsx rename to sites/components/datepicker/demo/demo1.tsx index 2f1c3566..f668569b 100644 --- a/devui/datepicker/doc-demo/demo1.tsx +++ b/sites/components/datepicker/demo/demo1.tsx @@ -1,8 +1,8 @@ -import { defineComponent, reactive } from 'vue' -import DatePicker from '../datepicker' +import { defineComponent, reactive, App } from 'vue' +import DatePicker from '../../../../devui/datepicker/datepicker' import './demo1.scss' -export default defineComponent({ +const Demo1 = defineComponent({ name: 'DDatepickerDemo1', setup() { const state = reactive<{ @@ -29,3 +29,9 @@ export default defineComponent({ } } }) + +export default { + install(app: App) { + app.component(Demo1.name, Demo1) + } +} \ No newline at end of file diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index 4f462751..6f50fcc3 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -1,3 +1,8 @@ +<script lang="ts"> +import * as Vue from 'vue' +import Demo1 from './demo/demo1' +</script> + # DatePicker 日期选择器 日期、时间可视化输入。 -- Gitee From c27a93aeec4ae243822683faec9572cd66c2ca6a Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Thu, 12 Aug 2021 11:11:08 +0800 Subject: [PATCH 34/42] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3demo=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sites/components/datepicker/demo/demo1.scss | 18 -- sites/components/datepicker/demo/demo1.tsx | 37 ----- sites/components/datepicker/index.md | 173 ++++++++++---------- 3 files changed, 84 insertions(+), 144 deletions(-) delete mode 100644 sites/components/datepicker/demo/demo1.scss delete mode 100644 sites/components/datepicker/demo/demo1.tsx diff --git a/sites/components/datepicker/demo/demo1.scss b/sites/components/datepicker/demo/demo1.scss deleted file mode 100644 index 391679da..00000000 --- a/sites/components/datepicker/demo/demo1.scss +++ /dev/null @@ -1,18 +0,0 @@ -.devui-datepicker-demo1 { - line-height: 32px; - font-size: 13px; - user-select: none; - - label { - margin-right: 5px; - border: 1px solid #aaaaaa; - padding: 5px 10px; - border-radius: 5px; - cursor: pointer; - - input[type='checkbox'] { - margin: 0; - transform: translateX(3px) translateY(2px); - } - } -} diff --git a/sites/components/datepicker/demo/demo1.tsx b/sites/components/datepicker/demo/demo1.tsx deleted file mode 100644 index f668569b..00000000 --- a/sites/components/datepicker/demo/demo1.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { defineComponent, reactive, App } from 'vue' -import DatePicker from '../../../../devui/datepicker/datepicker' -import './demo1.scss' - -const Demo1 = defineComponent({ - name: 'DDatepickerDemo1', - setup() { - const state = reactive<{ - range: boolean - showTime: boolean - }>({ - range: false, - showTime: true, - }) - return () => { - return (<div> - <div className="devui-datepicker-demo1"> - <label> - <span>区域选择</span> - <input type="checkbox" onChange={() => state.range = !state.range} checked={state.range} /> - </label> - <label> - <span>显示时间</span> - <input type="checkbox" onChange={() => state.showTime = !state.showTime} checked={state.showTime} /> - </label> - </div> - <DatePicker range={state.range} showTime={state.showTime} /> - </div>) - } - } -}) - -export default { - install(app: App) { - app.component(Demo1.name, Demo1) - } -} \ No newline at end of file diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index 6f50fcc3..be4fa9dc 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -1,6 +1,53 @@ +<style lang="scss"> +.devui-datepicker-demo { + margin: 10px 0px; + padding: 10px 0px; + + label { + border: 1px solid #aaa; + padding: 0px 5px; + height: 2em; + line-height: 2em; + display: inline-block; + margin: 5px; + font-size: 14px; + border-radius: 5px; + user-select: none; + cursor: pointer; + + input[type=checkbox] { + transform: translateY(1px); + margin-left: 3px; + } + } + + .input-binder { + width: 200px; + padding: 5px; + font-size: 16px; + border-radius: 5px; + } +} +</style> + <script lang="ts"> -import * as Vue from 'vue' -import Demo1 from './demo/demo1' +import { defineComponent, ref } from 'vue' +export default defineComponent({ + setup() { + const range = ref<boolean>(false) + const rangeSwitch = () => range.value = !range.value + + const showTime = ref<boolean>(false) + const showTimeSwitch = () => showTime.value = !showTime.value + + return { + range, + rangeSwitch, + showTime, + showTimeSwitch, + } + } +}) </script> # DatePicker 日期选择器 @@ -9,44 +56,61 @@ import Demo1 from './demo/demo1' ### 作为UI组件 +<section class="devui-datepicker-demo"> + <label>日期区间<input type="checkbox" @click="rangeSwitch" /></label> + <label>显示时间<input type="checkbox" @click="showTimeSwitch" /></label> + <d-datepicker :range="range" :show-time="showTime" /> +</section> + ```jsx -// 默认 range=false -<d-datepicker range={state.range} showTime={state.showTime} /> +<section class="devui-datepicker-demo"> + <label>日期区间<input type="checkbox" @click="rangeSwitch" /></label> + <label>显示时间<input type="checkbox" @click="showTimeSwitch" /></label> + <d-datepicker :range="range" :show-time="showTime" /> +</section> ``` -<d-datepicker-demo-1 /> - ### 绑定原生`<input>` 暂定通过`querySelector`查找节点,绑定真实`dom`节点。此方案待定。 ```jsx -// 选取完成后保留弹层 -<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input" /> -<d-datepicker attach-input-dom="#datepicker-input" /> +<section class="devui-datepicker-demo"> + <input class="input-binder" id="datepicker-input" /> + <d-datepicker attach-input-dom="#datepicker-input" /> +</section> ``` -<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input" /> -<d-datepicker attach-input-dom="#datepicker-input" /> +<section class="devui-datepicker-demo"> + <input class="input-binder" id="datepicker-input" /> + <d-datepicker attach-input-dom="#datepicker-input" /> +</section> ```jsx -// auto-close=true 选取完成后自动关闭 -<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-autoclose" /> -<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> +<section class="devui-datepicker-demo"> + <input class="input-binder" id="datepicker-input-autoclose" /> + <d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> +</section> ``` -<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-autoclose" /> -<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> +<section class="devui-datepicker-demo"> + <input class="input-binder" id="datepicker-input-autoclose" /> + <d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> +</section> ### 区域选择 ```jsx -<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-range" /> -<d-datepicker range attach-input-dom="#datepicker-input-range" /> +<section class="devui-datepicker-demo"> + <input class="input-binder" id="datepicker-input-range" /> + <d-datepicker range attach-input-dom="#datepicker-input-range" /> +</section> ``` -<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-range" /> -<d-datepicker range attach-input-dom="#datepicker-input-range" /> +<section class="devui-datepicker-demo"> + <input class="input-binder" id="datepicker-input-range" /> + <d-datepicker range attach-input-dom="#datepicker-input-range" /> +</section> ### Scroll位置跟踪 @@ -57,73 +121,4 @@ import Demo1 from './demo/demo1' TODO: 跟踪节流。 -```jsx -<div style="border:1px solid #aaa;height:300px;overflow:auto;"> - <br /> - // ... - <i>占行</i> - // ... - <input style="margin-left:100px;width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-tracing" /> - <d-datepicker auto-close range attach-input-dom="#datepicker-input-tracing" /> - // ... - <br /> - <i>占行</i> - // ... -</div> -``` - -<div style="border:1px solid #aaa;height:500px;overflow:auto;"> - <br /> - <i>占行</i> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <input style="margin-left:100px;width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-tracing" /> - <d-datepicker auto-close range attach-input-dom="#datepicker-input-tracing" /> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <br /> - <i>占行</i> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <i>占行</i> -</div> - -- Gitee From 4e30ff8e424694dfc8ff6c7b00744d52915f9fbe Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Thu, 12 Aug 2021 15:37:59 +0800 Subject: [PATCH 35/42] =?UTF-8?q?feat:=20className=E6=9B=B4=E6=94=B9?= =?UTF-8?q?=E4=B8=BAclass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/datepicker/components/calendar/index.tsx | 4 ++-- devui/datepicker/components/panel/index.tsx | 12 ++++++------ devui/datepicker/components/timepicker/index.tsx | 8 ++++---- devui/datepicker/components/toolbar/index.tsx | 6 +++--- .../datepicker/components/vertical-slider/index.tsx | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx index e2e36828..2834b42a 100644 --- a/devui/datepicker/components/calendar/index.tsx +++ b/devui/datepicker/components/calendar/index.tsx @@ -16,7 +16,7 @@ const Calendar = (props: TProps) => { next = new Date(current.getFullYear(), current.getMonth() + 1, 1) } return ( - <div className="devui-calendar-container"> + <div class="devui-calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} compare={next} /> { showTime ? <TimePicker time={current} /> : null } <CalendarDatePanel {...props} pos={1} current={next} compare={current} /> @@ -25,7 +25,7 @@ const Calendar = (props: TProps) => { ) } else { return ( - <div className="devui-calendar-container"> + <div class="devui-calendar-container"> <CalendarDatePanel {...props} pos={0} current={current} /> { showTime ? <TimePicker time={current} /> : null } </div> diff --git a/devui/datepicker/components/panel/index.tsx b/devui/datepicker/components/panel/index.tsx index 6af7f6bd..dca87ca2 100644 --- a/devui/datepicker/components/panel/index.tsx +++ b/devui/datepicker/components/panel/index.tsx @@ -6,7 +6,7 @@ import './index.scss' const CalendarDatePanel = (props: TDatePanelProps) => { return ( - <div className="devui-calendar-panel"> + <div class="devui-calendar-panel"> <Toolbar current={props.current} compare={props.compare} @@ -18,15 +18,15 @@ const CalendarDatePanel = (props: TDatePanelProps) => { onNextMonth={props.onNextMonth} onNextYear={props.onNextYear} /> - <ol className="head row">{ - WEEK_DAYS.map(day => <li className="cell">{day}</li>) + <ol class="head row">{ + WEEK_DAYS.map(day => <li class="cell">{day}</li>) }</ol> - <ul className="body">{ - getMonthWeeklyDays(props.current).map(row => <li className="row">{ + <ul class="body">{ + getMonthWeeklyDays(props.current).map(row => <li class="row">{ row.map(day => { return ( <span - className={cellClassName(props as TDatePanelProps, day)} + class={cellClassName(props as TDatePanelProps, day)} onClick={() => trigEvent(props as TDatePanelProps, day)} onMouseenter={() => handleDateEnter(props as TDatePanelProps, day)} >{day.date.getDate()}</span> diff --git a/devui/datepicker/components/timepicker/index.tsx b/devui/datepicker/components/timepicker/index.tsx index 727b06a7..df874f87 100644 --- a/devui/datepicker/components/timepicker/index.tsx +++ b/devui/datepicker/components/timepicker/index.tsx @@ -21,9 +21,9 @@ const TimePicker = defineComponent({ return () => { return ( - <div className="devui-calendar-timepicker"> - <div className="head"> - <div className="chars"> + <div class="devui-calendar-timepicker"> + <div class="head"> + <div class="chars"> {/* <span>{`:`}</span> <span>{`:`}</span> */} <span> @@ -33,7 +33,7 @@ const TimePicker = defineComponent({ </span> </div> </div> - <div className="select"> + <div class="select"> <VerticalSliderFunction items={hours} selectedIndex={state.hour} diff --git a/devui/datepicker/components/toolbar/index.tsx b/devui/datepicker/components/toolbar/index.tsx index ffc10f99..606d9368 100644 --- a/devui/datepicker/components/toolbar/index.tsx +++ b/devui/datepicker/components/toolbar/index.tsx @@ -16,7 +16,7 @@ const Item = (props: TCalendarToolbarItemProps) => { const className = `${disabled ? 'disabled' : ''}` const handleClick = disabled ? undefined : () => invokeCallback(props.cb, date, pos) return ( - <a className={className} onClick={handleClick}> + <a class={className} onClick={handleClick}> <Btn color={color} rotate={rotate} /> </a> ) @@ -25,7 +25,7 @@ const Item = (props: TCalendarToolbarItemProps) => { export const Title = (props: { date: Date; }) => { const { date } = props return ( - <a className="title">{ + <a class="title">{ `${date.getFullYear()}年${(date.getMonth() + 1 + '').padStart(2, '0')}月` }</a> ) @@ -52,7 +52,7 @@ const CalendarToolbar = (props: TDateToolbarProps) => { } return ( - <div className="devui-calendar-toolbar"> + <div class="devui-calendar-toolbar"> <Item disabled={dis[0]} date={current} pos={pos} button={Year} cb={onPreviousYear} /> <Item disabled={dis[1]} date={current} pos={pos} button={Month} rotate={-90} cb={onPreviousMonth} /> <Title date={current} /> diff --git a/devui/datepicker/components/vertical-slider/index.tsx b/devui/datepicker/components/vertical-slider/index.tsx index b374cb8e..07f0265d 100644 --- a/devui/datepicker/components/vertical-slider/index.tsx +++ b/devui/datepicker/components/vertical-slider/index.tsx @@ -91,8 +91,8 @@ const VerticalSlider = defineComponent({ return () => { return ( - <div ref={container} className={`devui-vertical-slider ${className}`}> - <div ref={movbar} className="movable-bar" style={{ + <div ref={container} class={`devui-vertical-slider ${className}`}> + <div ref={movbar} class="movable-bar" style={{ opacity: state.barOpacity, transform: `translateY(${state.y}px)`, transition: state.transition, @@ -100,12 +100,12 @@ const VerticalSlider = defineComponent({ { items.map((c, i) => { const className = i === state.selectedIndex ? itemClassSelected : itemClassNormal - return <span className={`slider-item ${className}`} style={{ height: `${size}px`, lineHeight: `${size}px` }}>{c}</span> + return <span class={`slider-item ${className}`} style={{ height: `${size}px`, lineHeight: `${size}px` }}>{c}</span> }) } </div> <div - className="slider-mask" + class="slider-mask" onMousedown={handleMouseDown} onMousemove={handleMouseMove} onMouseup={handleMouseUp} -- Gitee From 497bea4a22efe947a5952d723ab721f823401399 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Fri, 13 Aug 2021 00:10:06 +0800 Subject: [PATCH 36/42] feat: stick-slider --- devui/datepicker/index.ts | 4 +- devui/datepicker/stick-slider/index.scss | 47 +++++++++++++++++++ devui/datepicker/stick-slider/index.tsx | 59 ++++++++++++++++++++++++ sites/components/datepicker/index.md | 2 + 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 devui/datepicker/stick-slider/index.scss create mode 100644 devui/datepicker/stick-slider/index.tsx diff --git a/devui/datepicker/index.ts b/devui/datepicker/index.ts index 2a7ff4dd..2118ef84 100644 --- a/devui/datepicker/index.ts +++ b/devui/datepicker/index.ts @@ -1,11 +1,13 @@ import { App } from 'vue' import DatePicker from './datepicker' +import StickSlider from './stick-slider' -export { DatePicker } +export { DatePicker, StickSlider } export default { install(app: App) { DatePicker.version = '0.0.1' app.component(DatePicker.name, DatePicker) + app.component(StickSlider.name, StickSlider) } } diff --git a/devui/datepicker/stick-slider/index.scss b/devui/datepicker/stick-slider/index.scss new file mode 100644 index 00000000..ff1c840d --- /dev/null +++ b/devui/datepicker/stick-slider/index.scss @@ -0,0 +1,47 @@ +.devui-stick-slider { + border: 1px solid #000000; + display: flex; + flex-direction: row; + width: 240px; + height: 240px; + justify-content: center; + align-items: center; + border-radius: 100%; + position: relative; + + .main-button { + width: 36px; + height: 36px; + cursor: pointer; + user-select: none; + background-color: red; + border-radius: 100%; + position: relative; + z-index: 10; + } + + .sub-buttons { + border: 1px solid #0000ff; + width: 100%; + height: 100%; + position: absolute; + display: flex; + flex-direction: row; + flex-wrap: wrap; + + .button { + width: 48px; + height: 48px; + line-height: 48px; + text-align: center; + background-color: #ffaa00; + position: relative; + margin: 5px; + cursor: pointer; + + &.selected { + background-color: #ff3300; + } + } + } +} diff --git a/devui/datepicker/stick-slider/index.tsx b/devui/datepicker/stick-slider/index.tsx new file mode 100644 index 00000000..e716d3e9 --- /dev/null +++ b/devui/datepicker/stick-slider/index.tsx @@ -0,0 +1,59 @@ +import { defineComponent, reactive } from 'vue' + +import './index.scss' + +const StickSlider = defineComponent({ + name: 'DStickSlider', + props: {}, + setup(props) { + + const state = reactive({ + showButtons: false, + selectedIndex: 0, + }) + + const reset = () => { + state.showButtons = false + } + + const handleMainButtonMouseDown = (e: MouseEvent) => { + e.stopPropagation() + state.showButtons = true + } + const handleMainButtonMouseUp = (e: MouseEvent) => { + e.stopPropagation() + reset() + } + + return () => { + return ( + <div + class="devui-stick-slider" + onMousedown={handleMainButtonMouseDown} + onMouseup={handleMainButtonMouseUp} + onMouseleave={handleMainButtonMouseUp} + > + <div + class="sub-buttons" + style={{ display: state.showButtons ? '' : 'none' }} + > + { + Array(16).fill(null).map((_, i) => { + return (<div + class={`button ${i === state.selectedIndex ? 'selected' : ''}`} + onMouseenter={() => state.selectedIndex = i} + onMouseleave={() => state.selectedIndex = -1} + >[{i}]</div>) + }) + } + </div> + <div class="main-button"></div> + </div> + ) + } + } +}) + + + +export default StickSlider \ No newline at end of file diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index be4fa9dc..1786a9db 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -50,6 +50,8 @@ export default defineComponent({ }) </script> +<d-stick-slider /> + # DatePicker 日期选择器 日期、时间可视化输入。 -- Gitee From 318fe4cd5010781af1f39be635c259f854205df8 Mon Sep 17 00:00:00 2001 From: Marvin <imnull@outlook.com> Date: Fri, 13 Aug 2021 08:22:39 +0800 Subject: [PATCH 37/42] docs: stick-slider demo --- devui/datepicker/stick-slider/index.scss | 8 ++++---- devui/datepicker/stick-slider/index.tsx | 3 +-- sites/components/datepicker/index.md | 7 ++++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/devui/datepicker/stick-slider/index.scss b/devui/datepicker/stick-slider/index.scss index ff1c840d..46436f2f 100644 --- a/devui/datepicker/stick-slider/index.scss +++ b/devui/datepicker/stick-slider/index.scss @@ -2,12 +2,12 @@ border: 1px solid #000000; display: flex; flex-direction: row; - width: 240px; - height: 240px; justify-content: center; align-items: center; border-radius: 100%; position: relative; + width: 36px; + height: 36px; .main-button { width: 36px; @@ -22,8 +22,8 @@ .sub-buttons { border: 1px solid #0000ff; - width: 100%; - height: 100%; + width: 240px; + height: 240px; position: absolute; display: flex; flex-direction: row; diff --git a/devui/datepicker/stick-slider/index.tsx b/devui/datepicker/stick-slider/index.tsx index e716d3e9..d51ef64d 100644 --- a/devui/datepicker/stick-slider/index.tsx +++ b/devui/datepicker/stick-slider/index.tsx @@ -42,8 +42,7 @@ const StickSlider = defineComponent({ return (<div class={`button ${i === state.selectedIndex ? 'selected' : ''}`} onMouseenter={() => state.selectedIndex = i} - onMouseleave={() => state.selectedIndex = -1} - >[{i}]</div>) + >{i}</div>) }) } </div> diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md index 1786a9db..f99af509 100644 --- a/sites/components/datepicker/index.md +++ b/sites/components/datepicker/index.md @@ -50,7 +50,12 @@ export default defineComponent({ }) </script> -<d-stick-slider /> +# 扩展 + +<div style="margin:100px;"> + <d-stick-slider /> +</div> + # DatePicker 日期选择器 -- Gitee From b7fea612a8c7bbbc953df09a52964bb26d6422db Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Fri, 13 Aug 2021 09:12:44 +0800 Subject: [PATCH 38/42] =?UTF-8?q?fix:=20DatePicker=E5=B0=8F=E5=86=99?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=9B=B4=E6=94=B9=E4=B8=BAdate-picker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{datepicker => date-picker}/components/calendar/index.scss | 0 devui/{datepicker => date-picker}/components/calendar/index.tsx | 0 devui/{datepicker => date-picker}/components/helper.ts | 0 devui/{datepicker => date-picker}/components/panel/index.scss | 0 devui/{datepicker => date-picker}/components/panel/index.tsx | 0 .../components/timepicker/index.scss | 0 .../{datepicker => date-picker}/components/timepicker/index.tsx | 0 devui/{datepicker => date-picker}/components/toolbar/index.scss | 0 devui/{datepicker => date-picker}/components/toolbar/index.tsx | 0 .../{datepicker => date-picker}/components/toolbar/svg-icon.tsx | 0 devui/{datepicker => date-picker}/components/types.ts | 0 devui/{datepicker => date-picker}/components/utils.ts | 0 .../components/vertical-slider/index.scss | 0 .../components/vertical-slider/index.tsx | 0 .../datepicker.scss => date-picker/date-picker.scss} | 0 .../{datepicker/datepicker.tsx => date-picker/date-picker.tsx} | 2 +- devui/{datepicker => date-picker}/index.ts | 2 +- devui/{datepicker => date-picker}/stick-slider/index.scss | 0 devui/{datepicker => date-picker}/stick-slider/index.tsx | 0 devui/{datepicker => date-picker}/utils.ts | 0 sites/.vitepress/config/sidebar.ts | 2 +- sites/components/{datepicker => date-picker}/index.md | 0 22 files changed, 3 insertions(+), 3 deletions(-) rename devui/{datepicker => date-picker}/components/calendar/index.scss (100%) rename devui/{datepicker => date-picker}/components/calendar/index.tsx (100%) rename devui/{datepicker => date-picker}/components/helper.ts (100%) rename devui/{datepicker => date-picker}/components/panel/index.scss (100%) rename devui/{datepicker => date-picker}/components/panel/index.tsx (100%) rename devui/{datepicker => date-picker}/components/timepicker/index.scss (100%) rename devui/{datepicker => date-picker}/components/timepicker/index.tsx (100%) rename devui/{datepicker => date-picker}/components/toolbar/index.scss (100%) rename devui/{datepicker => date-picker}/components/toolbar/index.tsx (100%) rename devui/{datepicker => date-picker}/components/toolbar/svg-icon.tsx (100%) rename devui/{datepicker => date-picker}/components/types.ts (100%) rename devui/{datepicker => date-picker}/components/utils.ts (100%) rename devui/{datepicker => date-picker}/components/vertical-slider/index.scss (100%) rename devui/{datepicker => date-picker}/components/vertical-slider/index.tsx (100%) rename devui/{datepicker/datepicker.scss => date-picker/date-picker.scss} (100%) rename devui/{datepicker/datepicker.tsx => date-picker/date-picker.tsx} (99%) rename devui/{datepicker => date-picker}/index.ts (87%) rename devui/{datepicker => date-picker}/stick-slider/index.scss (100%) rename devui/{datepicker => date-picker}/stick-slider/index.tsx (100%) rename devui/{datepicker => date-picker}/utils.ts (100%) rename sites/components/{datepicker => date-picker}/index.md (100%) diff --git a/devui/datepicker/components/calendar/index.scss b/devui/date-picker/components/calendar/index.scss similarity index 100% rename from devui/datepicker/components/calendar/index.scss rename to devui/date-picker/components/calendar/index.scss diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/date-picker/components/calendar/index.tsx similarity index 100% rename from devui/datepicker/components/calendar/index.tsx rename to devui/date-picker/components/calendar/index.tsx diff --git a/devui/datepicker/components/helper.ts b/devui/date-picker/components/helper.ts similarity index 100% rename from devui/datepicker/components/helper.ts rename to devui/date-picker/components/helper.ts diff --git a/devui/datepicker/components/panel/index.scss b/devui/date-picker/components/panel/index.scss similarity index 100% rename from devui/datepicker/components/panel/index.scss rename to devui/date-picker/components/panel/index.scss diff --git a/devui/datepicker/components/panel/index.tsx b/devui/date-picker/components/panel/index.tsx similarity index 100% rename from devui/datepicker/components/panel/index.tsx rename to devui/date-picker/components/panel/index.tsx diff --git a/devui/datepicker/components/timepicker/index.scss b/devui/date-picker/components/timepicker/index.scss similarity index 100% rename from devui/datepicker/components/timepicker/index.scss rename to devui/date-picker/components/timepicker/index.scss diff --git a/devui/datepicker/components/timepicker/index.tsx b/devui/date-picker/components/timepicker/index.tsx similarity index 100% rename from devui/datepicker/components/timepicker/index.tsx rename to devui/date-picker/components/timepicker/index.tsx diff --git a/devui/datepicker/components/toolbar/index.scss b/devui/date-picker/components/toolbar/index.scss similarity index 100% rename from devui/datepicker/components/toolbar/index.scss rename to devui/date-picker/components/toolbar/index.scss diff --git a/devui/datepicker/components/toolbar/index.tsx b/devui/date-picker/components/toolbar/index.tsx similarity index 100% rename from devui/datepicker/components/toolbar/index.tsx rename to devui/date-picker/components/toolbar/index.tsx diff --git a/devui/datepicker/components/toolbar/svg-icon.tsx b/devui/date-picker/components/toolbar/svg-icon.tsx similarity index 100% rename from devui/datepicker/components/toolbar/svg-icon.tsx rename to devui/date-picker/components/toolbar/svg-icon.tsx diff --git a/devui/datepicker/components/types.ts b/devui/date-picker/components/types.ts similarity index 100% rename from devui/datepicker/components/types.ts rename to devui/date-picker/components/types.ts diff --git a/devui/datepicker/components/utils.ts b/devui/date-picker/components/utils.ts similarity index 100% rename from devui/datepicker/components/utils.ts rename to devui/date-picker/components/utils.ts diff --git a/devui/datepicker/components/vertical-slider/index.scss b/devui/date-picker/components/vertical-slider/index.scss similarity index 100% rename from devui/datepicker/components/vertical-slider/index.scss rename to devui/date-picker/components/vertical-slider/index.scss diff --git a/devui/datepicker/components/vertical-slider/index.tsx b/devui/date-picker/components/vertical-slider/index.tsx similarity index 100% rename from devui/datepicker/components/vertical-slider/index.tsx rename to devui/date-picker/components/vertical-slider/index.tsx diff --git a/devui/datepicker/datepicker.scss b/devui/date-picker/date-picker.scss similarity index 100% rename from devui/datepicker/datepicker.scss rename to devui/date-picker/date-picker.scss diff --git a/devui/datepicker/datepicker.tsx b/devui/date-picker/date-picker.tsx similarity index 99% rename from devui/datepicker/datepicker.tsx rename to devui/date-picker/date-picker.tsx index fb124a95..59f36770 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/date-picker/date-picker.tsx @@ -5,7 +5,7 @@ import { traceNode, invokeFunction, } from './utils' import Calendar from './components/calendar' -import './datepicker.scss' +import './date-picker.scss' type TState = { range?: boolean diff --git a/devui/datepicker/index.ts b/devui/date-picker/index.ts similarity index 87% rename from devui/datepicker/index.ts rename to devui/date-picker/index.ts index 2118ef84..9f3c7f0e 100644 --- a/devui/datepicker/index.ts +++ b/devui/date-picker/index.ts @@ -1,5 +1,5 @@ import { App } from 'vue' -import DatePicker from './datepicker' +import DatePicker from './date-picker' import StickSlider from './stick-slider' export { DatePicker, StickSlider } diff --git a/devui/datepicker/stick-slider/index.scss b/devui/date-picker/stick-slider/index.scss similarity index 100% rename from devui/datepicker/stick-slider/index.scss rename to devui/date-picker/stick-slider/index.scss diff --git a/devui/datepicker/stick-slider/index.tsx b/devui/date-picker/stick-slider/index.tsx similarity index 100% rename from devui/datepicker/stick-slider/index.tsx rename to devui/date-picker/stick-slider/index.tsx diff --git a/devui/datepicker/utils.ts b/devui/date-picker/utils.ts similarity index 100% rename from devui/datepicker/utils.ts rename to devui/date-picker/utils.ts diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index 0cc0cdde..da971953 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -49,7 +49,7 @@ const sidebar = { { text: 'Cascader 级联菜单', link: '/components/cascader/' }, { text: 'CategorySearch 分类搜索', link: '/components/category-search/' }, { text: 'Checkbox 复选框', link: '/components/checkbox/' }, - { text: 'DatePicker 日期选择器', link: '/components/datepicker/' }, + { text: 'DatePicker 日期选择器', link: '/components/date-picker/' }, { text: 'DatePickerPro 日期选择器', link: '/components/date-picker-pro/' }, { text: 'EditableSelect 可编辑下拉框', link: '/components/editable-select/' }, { text: 'Form 表单', link: '/components/form/' }, diff --git a/sites/components/datepicker/index.md b/sites/components/date-picker/index.md similarity index 100% rename from sites/components/datepicker/index.md rename to sites/components/date-picker/index.md -- Gitee From c1995b1f982da6e9a587f0d98c2da84968dd9acf Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Fri, 13 Aug 2021 09:21:19 +0800 Subject: [PATCH 39/42] fix: delete scripts/get-doc-demos.js --- scripts/generate-devui.js | 7 ------- scripts/get-doc-demos.js | 44 --------------------------------------- 2 files changed, 51 deletions(-) delete mode 100644 scripts/get-doc-demos.js diff --git a/scripts/generate-devui.js b/scripts/generate-devui.js index b180ce90..34976e1a 100644 --- a/scripts/generate-devui.js +++ b/scripts/generate-devui.js @@ -9,8 +9,6 @@ console.log('config:', config); let importStr = `import { App } from 'vue';\n\n`; const components = []; -const getDocDemos = require('./get-doc-demos') - config['/'].forEach(({ text: ctext, children }) => { if (ctext !== '快速开始') { importStr += `// ${ctext}\n`; @@ -22,11 +20,6 @@ config['/'].forEach(({ text: ctext, children }) => { const filename = linkItem[1]; importStr += `import ${name} from './${filename}';\n`; components.push(name); - - getDocDemos(filename).forEach(({ name, from }) => { - importStr += `import ${name} from './${from}';\n`; - components.push(name); - }) }) importStr += `\n`; } diff --git a/scripts/get-doc-demos.js b/scripts/get-doc-demos.js deleted file mode 100644 index deac9748..00000000 --- a/scripts/get-doc-demos.js +++ /dev/null @@ -1,44 +0,0 @@ -const path = require('path'); -const fs = require('fs'); - -const UI_BASE_DIR = path.resolve(__dirname, '../devui') - -/** - * 读取UI组件库中的`doc-demo`目录,收集供文档使用的`demo`组件。 - * - mrundef - 210811 - * @param {string} compName 组件名称 - * @param {string} docDemoDir demo组件存放目录名称,默认`doc-demo` - * @returns - */ -const getDocDemos = (compName, docDemoDir = 'doc-demo') => { - const res = [] - /** 组装`doc-demo`路径 */ - const docDemoPath = path.join(UI_BASE_DIR, compName, docDemoDir) - /** 路径存在 且是目录 */ - if (fs.existsSync(docDemoPath) && fs.statSync(docDemoPath).isDirectory()) { - /** 获取有效组件文件 */ - const names = fs.readdirSync(docDemoPath, 'utf-8') - // 过滤隐藏文件名 - .filter(n => !/^\./.test(n)) - // 仅保留文件 - .filter(n => fs.statSync(path.join(docDemoPath, n)).isFile()) - // 过滤文件类型 js/jsx/ts/tsx/vue - .filter(n => /\.(([jt]sx?|vue)?)$/.test(n)) - names.forEach(name => { - /** 去掉扩展名 */ - const n = name.replace(/\.[^\.\\\/]+$/, '') - /** 组装引入命名名称 */ - const k = `${compName}_${n}` - /** 确保没有重复引用 */ - if (!res.some(({ name }) => name === k)) { - res.push({ - from: `${compName}/doc-demo/${n}`, - name: k - }) - } - }) - } - return res -} - -module.exports = getDocDemos \ No newline at end of file -- Gitee From d57b640eb95445d0b112dec867674ed6e3123776 Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Fri, 13 Aug 2021 12:27:39 +0800 Subject: [PATCH 40/42] =?UTF-8?q?fix:=20DatePicker=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=92=8C=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/date-picker/date-picker.tsx | 192 +++++++------------------- devui/date-picker/helper.ts | 151 ++++++++++++++++++++ sites/components/date-picker/index.md | 84 ++++++----- 3 files changed, 251 insertions(+), 176 deletions(-) create mode 100644 devui/date-picker/helper.ts diff --git a/devui/date-picker/date-picker.tsx b/devui/date-picker/date-picker.tsx index 59f36770..1d123b7c 100644 --- a/devui/date-picker/date-picker.tsx +++ b/devui/date-picker/date-picker.tsx @@ -1,95 +1,21 @@ import { defineComponent, reactive, onMounted, onUnmounted, ref } from 'vue' import { - EventManager, - formatDate, formatRange, isIn, + EventManager, isIn, traceNode, invokeFunction, } from './utils' -import Calendar from './components/calendar' -import './date-picker.scss' - -type TState = { - range?: boolean - current?: Date - next?: Date - start?: Date - end?: Date - hover?: Date - show?: boolean - input?: string - st?: boolean -} - -/** - * Calendar 面板年月切换逻辑 - * @param state - * @param index - * @param pos - * @param date - */ -const handleCalendarSwitchState = (state: TState, index: number, pos: number, date: Date) => { - switch(index) { - case 0: // previous year - const preYear = new Date(date) - preYear.setFullYear(preYear.getFullYear() - 1) - pos === 0 ? (state.current = preYear) : (state.next = preYear) - break - case 1: // previous month - const preMonth = new Date(date) - preMonth.setMonth(preMonth.getMonth() - 1) - pos === 0 ? (state.current = preMonth) : (state.next = preMonth) - break - case 2: // next month - const nextMonth = new Date(date) - nextMonth.setMonth(nextMonth.getMonth() + 1) - pos === 0 ? (state.current = nextMonth) : (state.next = nextMonth) - break - case 3: // next year - const nextYear = new Date(date) - nextYear.setFullYear(nextYear.getFullYear() + 1) - pos === 0 ? (state.current = nextYear) : (state.next = nextYear) - break - } -} -/** - * 格式化日期 - * @param state - * @param props - * @returns - */ -const formatOutputValue = (state: TState, props: any) => { - const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} - if(range) { - return formatRange(format, - state.start, - state.end, - rangeSpliter - ) - } else { - return formatDate(format, state.start) - } -} +import { + TState, + handlePositionFactory, + handleCalendarSwitchState, + formatValue, + formatPlaceholder, + getAttachInputDom, +} from './helper' -const getPlaceholder = (props: any) => { - if(!props) return '' - const format = props.format || `y/MM/dd` - const sp = props.rangeSpliter || '-' - return props.range ? `${format} ${sp} ${format}` : format -} +import Calendar from './components/calendar' -/** - * 输出日期选择结果 - * @param id - * @param output - */ -const handleDomOutput = (id: string | undefined, output: string) => { - if(id && typeof id === 'string') { - const el = document.querySelector(id) - if(el instanceof HTMLInputElement) { - el.value = output - } - } -} +import './date-picker.scss' export default defineComponent({ name: 'DDatepicker', @@ -106,7 +32,6 @@ export default defineComponent({ const container = ref<Element>() const evtman = new EventManager() - const current = new Date() const state = reactive<TState>({ @@ -115,65 +40,50 @@ export default defineComponent({ next: new Date(current.getFullYear(), current.getMonth() + 1, 1), show: false, input: props.attachInputDom, - st: true - }) - - const pos = reactive<{ - x: string - y: string - }>({ + st: true, x: '0', y: '0', }) - /** - * 获取绑定节点 - * @returns - */ - const getAttachInputDom = () => { - const { attachInputDom } = props || {} - if(!attachInputDom || typeof attachInputDom !== 'string') { - return null - } - const el = document.querySelector(attachInputDom) - if(!el) { - return null + // 弹出层跟踪 + const handlePosition = handlePositionFactory(state, props, container) + + // 绑定层显示值、placehoder值设置 + const setBindingDom = (el: any = getAttachInputDom(state, props)) => { + + const value = formatValue(state, props) + const placeholder = formatPlaceholder(props) + + // 判断节点原生DOM类型 + // 对input节点的值处理 + if (el instanceof HTMLInputElement) { + // 设置水印文字 + el.placeholder = placeholder + // 设置显示值 + el.value = value + return el.value } - state.st = false - return el + return value } - /** - * 绑定弹出层场景,计算弹出层位置。 - * @returns - */ - const handlePosition = () => { - if(!state.show) { - pos.x = `-100%` - pos.y = `-100%` - return - } - const el = getAttachInputDom() - if(!el) { - return - } - const { left, top, width, height } = el.getBoundingClientRect() - const { width: _width, height: _height } = container.value.getBoundingClientRect() - const bottom = window.innerHeight - top - height - pos.x = `${left}px` - if(bottom > top) { - pos.y = `${top + height}px` + 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 { - pos.y = `${top - _height}px` + state.end = null } - } onMounted(() => { // 获取绑定节点(默认input) - const el = getAttachInputDom() + const el = getAttachInputDom(state, props) // 绑定节点不存在,作为普通组件展示。 - if(!el) { + if (!el) { // 显示组件 state.show = true return @@ -182,21 +92,17 @@ export default defineComponent({ state.show = false } - // 判断节点原生DOM类型 - // 对input节点的值处理 - if(el instanceof HTMLInputElement) { - // 设置水印文字 - el.placeholder = getPlaceholder(props) - } + 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)) { + if (!state.show || e.target === el || isIn(e.target as Node, container.value)) { return } state.show = false + reset() }) // 对绑定节点做scroll跟踪,并绑定跟踪事件 traceNode(el).forEach(node => { @@ -210,13 +116,14 @@ export default defineComponent({ return () => { handlePosition() + setBindingDom() return ( <div class={state.st ? `` : `devui-datepicker-global-viewport`}> <div ref={container} class="devui-datepicker-container" style={{ - transform: state.st ? '' : `translateX(${pos.x}) translateY(${pos.y})` + transform: state.st ? '' : `translateX(${state.x}) translateY(${state.y})` }} > <Calendar @@ -228,14 +135,13 @@ export default defineComponent({ dateEnd={state.end} dateHover={state.hover} onReset={(date: Date) => { - state.end = state.hover = undefined + state.current = state.end = state.hover = undefined state.start = date }} onChange={() => { - const output = formatOutputValue(state, props) - handleDomOutput(state.input, output) + const output = setBindingDom() invokeFunction(props.selectedDateChange, output) - if(props.autoClose) { + if (props.autoClose) { state.show = false } }} diff --git a/devui/date-picker/helper.ts b/devui/date-picker/helper.ts new file mode 100644 index 00000000..0bc57373 --- /dev/null +++ b/devui/date-picker/helper.ts @@ -0,0 +1,151 @@ +import type { Ref } from 'vue' +import { formatDate, formatRange } from './utils' + +export type TState = { + range?: boolean + current?: Date + next?: Date + start?: Date + end?: Date + hover?: Date + show?: boolean + input?: string + st?: boolean + x?: string + y?: string +} + +/** + * Calendar 面板年月切换逻辑 + * @param state + * @param index + * @param pos + * @param date + */ +export const handleCalendarSwitchState = (state: TState, index: number, pos: number, date: Date) => { + switch (index) { + case 0: // previous year + const preYear = new Date(date) + preYear.setFullYear(preYear.getFullYear() - 1) + pos === 0 ? (state.current = preYear) : (state.next = preYear) + break + case 1: // previous month + const preMonth = new Date(date) + preMonth.setMonth(preMonth.getMonth() - 1) + pos === 0 ? (state.current = preMonth) : (state.next = preMonth) + break + case 2: // next month + const nextMonth = new Date(date) + nextMonth.setMonth(nextMonth.getMonth() + 1) + pos === 0 ? (state.current = nextMonth) : (state.next = nextMonth) + break + case 3: // next year + const nextYear = new Date(date) + nextYear.setFullYear(nextYear.getFullYear() + 1) + pos === 0 ? (state.current = nextYear) : (state.next = nextYear) + break + } +} + +/** + * 格式化输入日期字符串 + * @param state + * @param props + * @returns + */ +export const formatValue = (state: TState, props: any) => { + const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} + if (range) { + if (!state.start) { + return '' + } else if(!state.end) { + return formatDate(format, state.start) + } + if(state.end < state.start) { + const end = state.end + state.end = state.start + state.start = end + } + return formatRange(format, + state.start, + state.end, + rangeSpliter + ) + } else { + if (!state.start) { + return '' + } + return formatDate(format, state.start) + } +} + +/** + * 格式化placeholder显示 + * @param props + * @returns + */ +export const formatPlaceholder = (props: any) => { + if (!props) return '' + const format = props.format || `y/MM/dd` + const sp = props.rangeSpliter || '-' + return props.range ? `${format} ${sp} ${format}` : format +} + +/** + * 输出日期选择结果 + * @param id + * @param output + */ +export const handleValue = (id: string | undefined, output: string) => { + if (id && typeof id === 'string') { + const el = document.querySelector(id) + if (el instanceof HTMLInputElement) { + el.value = output + } + } +} + +/** + * 获取绑定节点 + * @returns + */ +export const getAttachInputDom = (state: TState, props: any) => { + const { attachInputDom } = props || {} + if (!attachInputDom || typeof attachInputDom !== 'string') { + return null + } + const el = document.querySelector(attachInputDom) + if (!el) { + return null + } + state.st = false + return el +} + +/** + * 绑定弹出层场景,计算弹出层位置。 + * @param state + * @param props + * @param container + * @returns + */ +export const handlePositionFactory = (state: TState, props: any, container: Ref<Element>) => () => { + if (!state.show) { + state.x = `-100%` + state.y = `-100%` + return + } + const el = getAttachInputDom(state, props) + if (!el) { + return + } + const { left, top, width, height } = el.getBoundingClientRect() + const { width: _width, height: _height } = container.value.getBoundingClientRect() + const bottom = window.innerHeight - top - height + state.x = `${left}px` + if (bottom > top) { + state.y = `${top + height}px` + } else { + state.y = `${top - _height}px` + } +} \ No newline at end of file diff --git a/sites/components/date-picker/index.md b/sites/components/date-picker/index.md index f99af509..90af930b 100644 --- a/sites/components/date-picker/index.md +++ b/sites/components/date-picker/index.md @@ -22,7 +22,7 @@ } .input-binder { - width: 200px; + width: 300px; padding: 5px; font-size: 16px; border-radius: 5px; @@ -37,14 +37,30 @@ export default defineComponent({ const range = ref<boolean>(false) const rangeSwitch = () => range.value = !range.value + const range2 = ref<boolean>(false) + const rangeSwitch2 = () => range2.value = !range2.value + const showTime = ref<boolean>(false) const showTimeSwitch = () => showTime.value = !showTime.value + const spliter = ref<boolean>('-') + const setSpliter = (v: string) => spliter.value = v + + const handleRangeChange = (e: Event) => { + const { selectedIndex, value } = e.target + setSpliter(value) + } + return { range, rangeSwitch, + range2, + rangeSwitch2, showTime, showTimeSwitch, + spliter, + setSpliter, + handleRangeChange, } } }) @@ -65,15 +81,13 @@ export default defineComponent({ <section class="devui-datepicker-demo"> <label>日期区间<input type="checkbox" @click="rangeSwitch" /></label> - <label>显示时间<input type="checkbox" @click="showTimeSwitch" /></label> - <d-datepicker :range="range" :show-time="showTime" /> + <d-datepicker :range="range" /> </section> ```jsx <section class="devui-datepicker-demo"> <label>日期区间<input type="checkbox" @click="rangeSwitch" /></label> - <label>显示时间<input type="checkbox" @click="showTimeSwitch" /></label> - <d-datepicker :range="range" :show-time="showTime" /> + <d-datepicker :range="range" /> </section> ``` @@ -90,35 +104,18 @@ export default defineComponent({ <section class="devui-datepicker-demo"> <input class="input-binder" id="datepicker-input" /> - <d-datepicker attach-input-dom="#datepicker-input" /> -</section> - -```jsx -<section class="devui-datepicker-demo"> - <input class="input-binder" id="datepicker-input-autoclose" /> - <d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> + <label>日期区间<input type="checkbox" @click="rangeSwitch2" /></label> + <label>分隔符 + <select @change="handleRangeChange" :disabled="!range2"> + <option>-</option> + <option>~</option> + <option>--</option> + <option>~</option> + <option>***</option> + </select> + </label> + <d-datepicker attach-input-dom="#datepicker-input" :range="range2" :range-spliter="spliter" /> </section> -``` - -<section class="devui-datepicker-demo"> - <input class="input-binder" id="datepicker-input-autoclose" /> - <d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> -</section> - -### 区域选择 - -```jsx -<section class="devui-datepicker-demo"> - <input class="input-binder" id="datepicker-input-range" /> - <d-datepicker range attach-input-dom="#datepicker-input-range" /> -</section> -``` - -<section class="devui-datepicker-demo"> - <input class="input-binder" id="datepicker-input-range" /> - <d-datepicker range attach-input-dom="#datepicker-input-range" /> -</section> - ### Scroll位置跟踪 @@ -128,4 +125,25 @@ export default defineComponent({ TODO: 跟踪节流。 +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> +<br /> -- Gitee From b6660a708e066d1ce397ac15668826c0d579986b Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Fri, 13 Aug 2021 16:37:33 +0800 Subject: [PATCH 41/42] =?UTF-8?q?docs:=20=E7=AE=80=E5=8C=96date-picker?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sites/components/date-picker/index.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/sites/components/date-picker/index.md b/sites/components/date-picker/index.md index 90af930b..9f60b9c5 100644 --- a/sites/components/date-picker/index.md +++ b/sites/components/date-picker/index.md @@ -79,16 +79,27 @@ export default defineComponent({ ### 作为UI组件 +--------- + + +#### 最简 + <section class="devui-datepicker-demo"> - <label>日期区间<input type="checkbox" @click="rangeSwitch" /></label> - <d-datepicker :range="range" /> + <d-datepicker /> </section> ```jsx +<d-datepicker /> +``` + +#### 区间 + <section class="devui-datepicker-demo"> - <label>日期区间<input type="checkbox" @click="rangeSwitch" /></label> - <d-datepicker :range="range" /> + <d-datepicker range /> </section> + +```jsx +<d-datepicker range /> ``` ### 绑定原生`<input>` @@ -104,7 +115,6 @@ export default defineComponent({ <section class="devui-datepicker-demo"> <input class="input-binder" id="datepicker-input" /> - <label>日期区间<input type="checkbox" @click="rangeSwitch2" /></label> <label>分隔符 <select @change="handleRangeChange" :disabled="!range2"> <option>-</option> @@ -114,7 +124,7 @@ export default defineComponent({ <option>***</option> </select> </label> - <d-datepicker attach-input-dom="#datepicker-input" :range="range2" :range-spliter="spliter" /> + <d-datepicker attach-input-dom="#datepicker-input" range :range-spliter="spliter" /> </section> ### Scroll位置跟踪 -- Gitee From 75086a4383e11dfa21d72cefd274027cf62dbf3a Mon Sep 17 00:00:00 2001 From: mrundef <imnull@outlook.com> Date: Fri, 13 Aug 2021 17:35:57 +0800 Subject: [PATCH 42/42] =?UTF-8?q?feat:=20DatePicker=E5=8D=95=E6=9D=BF/?= =?UTF-8?q?=E5=8F=8C=E6=9D=BF=E6=97=A5=E6=9C=9F=E9=99=90=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=A0=8F=E5=B9=B4=E6=9C=88=E9=81=8D=E5=8E=86?= =?UTF-8?q?=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/date-picker/components/helper.ts | 14 ++++++++ devui/date-picker/components/panel/index.tsx | 2 ++ .../date-picker/components/toolbar/index.tsx | 13 +++++++- devui/date-picker/components/types.ts | 2 ++ devui/date-picker/components/utils.ts | 33 +++++++++++++++++++ devui/date-picker/date-picker.tsx | 5 +++ devui/date-picker/utils.ts | 7 ++++ sites/components/date-picker/index.md | 22 +++++++++++-- 8 files changed, 95 insertions(+), 3 deletions(-) diff --git a/devui/date-picker/components/helper.ts b/devui/date-picker/components/helper.ts index 5a06ae17..a756d082 100644 --- a/devui/date-picker/components/helper.ts +++ b/devui/date-picker/components/helper.ts @@ -9,6 +9,13 @@ export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = 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) { + return `${base} disabled` + } const key = getDateKey(day.date) if (props.type === 'range') { if (props.dateStart) { @@ -57,6 +64,13 @@ export const handleDateEnter = (props: TDatePanelProps, day: TDateCell): void => if(day.current !== 0) { return } + const { dateMin, dateMax } = props + if(dateMin && subDateDay(day.date, dateMin) < 0) { + return + } + if(dateMax && subDateDay(dateMax, day.date) < 0) { + return + } if (props.type === 'range') { const key = getDateKey(day.date) if (!props.dateStart || getDateKey(props.dateStart) === key || props.dateEnd) { diff --git a/devui/date-picker/components/panel/index.tsx b/devui/date-picker/components/panel/index.tsx index dca87ca2..fdef8292 100644 --- a/devui/date-picker/components/panel/index.tsx +++ b/devui/date-picker/components/panel/index.tsx @@ -17,6 +17,8 @@ const CalendarDatePanel = (props: TDatePanelProps) => { onPreviousMonth={props.onPreviousMonth} onNextMonth={props.onNextMonth} onNextYear={props.onNextYear} + dateMax={props.dateMax} + dateMin={props.dateMin} /> <ol class="head row">{ WEEK_DAYS.map(day => <li class="cell">{day}</li>) diff --git a/devui/date-picker/components/toolbar/index.tsx b/devui/date-picker/components/toolbar/index.tsx index 606d9368..802ba167 100644 --- a/devui/date-picker/components/toolbar/index.tsx +++ b/devui/date-picker/components/toolbar/index.tsx @@ -1,4 +1,4 @@ -import { compareDate, invokeCallback } from '../utils' +import { compareDate, invokeCallback, subDateMonth } from '../utils' import { Year, Month } from './svg-icon' import { TCalendarToolbarItemProps, TDateToolbarProps } from '../types' import './index.scss' @@ -34,6 +34,7 @@ export const Title = (props: { date: Date; }) => { const CalendarToolbar = (props: TDateToolbarProps) => { const { type, current, compare, pos, + dateMax, dateMin, onPreviousYear, onPreviousMonth, onNextMonth, @@ -41,14 +42,24 @@ const CalendarToolbar = (props: TDateToolbarProps) => { } = props const dis = [false, false, false, false] + if (type === 'range') { if (pos === 1) { dis[0] = !compareDate(compare, current, 'year', 1) dis[1] = !compareDate(compare, current, 'month', 1) + dis[2] = !compareDate(current, dateMax, 'month', 0) + dis[3] = !compareDate(current, dateMax, 'year', 0) } else { + dis[0] = !compareDate(dateMin, current, 'year', 0) + dis[1] = !compareDate(dateMin, current, 'month', 0) dis[2] = !compareDate(current, compare, 'month', 1) dis[3] = !compareDate(current, compare, 'year', 1) } + } else { + dis[0] = !compareDate(dateMin, current, 'year', 0) + dis[1] = !compareDate(dateMin, current, 'month', 0) + dis[2] = !compareDate(current, dateMax, 'month', 0) + dis[3] = !compareDate(current, dateMax, 'year', 0) } return ( diff --git a/devui/date-picker/components/types.ts b/devui/date-picker/components/types.ts index e366e168..66db1520 100644 --- a/devui/date-picker/components/types.ts +++ b/devui/date-picker/components/types.ts @@ -8,6 +8,8 @@ export type TDateConfig = { mode?: TDatePanelMode current: Date showTime: boolean + dateMin?: Date + dateMax?: Date } export type TDateSelectingBase = { diff --git a/devui/date-picker/components/utils.ts b/devui/date-picker/components/utils.ts index 280fae28..cb2c02a5 100644 --- a/devui/date-picker/components/utils.ts +++ b/devui/date-picker/components/utils.ts @@ -114,3 +114,36 @@ export const compareDate = (small: Date | undefined, big: Date | undefined, mode } } +export const parseDate = (str?: string) : Date | null => { + if(!str || typeof str !== 'string') { + return null + } + + const [dateStr = '', timeStr = ''] = str.split(/([ ]|T)+/) + if(!dateStr) { + return null + } + const [y, m, d] = dateStr.split(/[^\d]+/) + const year = _parseInt(y), month = _parseInt(m), date = _parseInt(d) || 1 + if(!year || !month) { + return null + } + const time = parseTime(timeStr) + return new Date(year, month - 1, date, ...time) +} + +const _parseInt = (str: any, dftVal?: number) => { + if(!str || typeof str !== 'string') { + return dftVal + } + const n = parseInt(str) + if(isNaN(n)) { + return dftVal + } + return n +} + +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)] +} \ No newline at end of file diff --git a/devui/date-picker/date-picker.tsx b/devui/date-picker/date-picker.tsx index 1d123b7c..c8285aca 100644 --- a/devui/date-picker/date-picker.tsx +++ b/devui/date-picker/date-picker.tsx @@ -16,6 +16,7 @@ import { import Calendar from './components/calendar' import './date-picker.scss' +import { parseDate } from './components/utils' export default defineComponent({ name: 'DDatepicker', @@ -27,6 +28,8 @@ export default defineComponent({ format: { type: String, default: 'y/MM/dd' }, rangeSpliter: { type: String, default: '-' }, attachInputDom: { type: String }, + dateMin: { type: String }, + dateMax: { type: String }, }, setup(props, ctx) { @@ -131,6 +134,8 @@ export default defineComponent({ showTime={props.showTime} current={state.current} next={state.next} + dateMin={parseDate(props.dateMin)} + dateMax={parseDate(props.dateMax)} dateStart={state.start} dateEnd={state.end} dateHover={state.hover} diff --git a/devui/date-picker/utils.ts b/devui/date-picker/utils.ts index aebc7b07..0c671520 100644 --- a/devui/date-picker/utils.ts +++ b/devui/date-picker/utils.ts @@ -100,4 +100,11 @@ export const invokeFunction = (fn: any, ...args: any[]) => { if (typeof fn === 'function') { fn(...args) } +} + +export const getMinDate = (a?: Date, b?: Date) => { + if(a && b) { + return a > b ? b : a + } + return a || b || undefined } \ No newline at end of file diff --git a/sites/components/date-picker/index.md b/sites/components/date-picker/index.md index 9f60b9c5..bd1253b0 100644 --- a/sites/components/date-picker/index.md +++ b/sites/components/date-picker/index.md @@ -91,8 +91,7 @@ export default defineComponent({ ```jsx <d-datepicker /> ``` - -#### 区间 +#### 区域选择 <section class="devui-datepicker-demo"> <d-datepicker range /> @@ -102,6 +101,25 @@ export default defineComponent({ <d-datepicker range /> ``` +### 区间限制 + +<section class="devui-datepicker-demo"> + <d-datepicker date-min="2021-8-9" date-max="2021-9-20" /> +</section> + +```jsx +<d-datepicker date-min="2021-8-9" date-max="2021-9-20" /> +``` + +<section class="devui-datepicker-demo"> + <d-datepicker range date-min="2021-8-9" date-max="2022-3-20" /> +</section> + +```jsx +<d-datepicker range date-min="2021-8-9" date-max="2022-3-20" /> +``` + + ### 绑定原生`<input>` 暂定通过`querySelector`查找节点,绑定真实`dom`节点。此方案待定。 -- Gitee