From 939d45010ad1be957c2c637d0f8dc5d52a146bc9 Mon Sep 17 00:00:00 2001 From: "154239735@qq.com" <154239735@qq.com> Date: Sun, 19 Sep 2021 00:36:28 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=89=88timePicker=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/time-picker/index.ts | 18 + devui/time-picker/src/time-picker-types.ts | 32 ++ devui/time-picker/src/time-picker.scss | 117 ++++++ devui/time-picker/src/time-picker.tsx | 393 +++++++++++++++++++++ devui/time-picker/src/utils.ts | 68 ++++ sites/.vitepress/config/sidebar.ts | 2 +- sites/components/time-picker/index.md | 176 +++++++++ 7 files changed, 805 insertions(+), 1 deletion(-) create mode 100644 devui/time-picker/index.ts create mode 100644 devui/time-picker/src/time-picker-types.ts create mode 100644 devui/time-picker/src/time-picker.scss create mode 100644 devui/time-picker/src/time-picker.tsx create mode 100644 devui/time-picker/src/utils.ts create mode 100644 sites/components/time-picker/index.md diff --git a/devui/time-picker/index.ts b/devui/time-picker/index.ts new file mode 100644 index 00000000..aaab8f20 --- /dev/null +++ b/devui/time-picker/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' +import TimePicker from './src/time-picker' + +TimePicker.install = function(app: App): void { + app.component(TimePicker.name, TimePicker) +} + +export { TimePicker } + +export default { + title: 'TimePicker 时间选择器', + category: '数据录入', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + + app.use(TimePicker as any) + } +} diff --git a/devui/time-picker/src/time-picker-types.ts b/devui/time-picker/src/time-picker-types.ts new file mode 100644 index 00000000..a033c75b --- /dev/null +++ b/devui/time-picker/src/time-picker-types.ts @@ -0,0 +1,32 @@ +import { ExtractPropTypes, PropType } from 'vue'; + +export const timePickerProps = { + initTime: { + type: String, + default: '00:00:00' + }, + placeholder: { + type: String, + default: '00:00:00' + }, + disabled: { + type: Boolean, + default: false + }, + timePickerWidth: { + type: Number, + default: 210 + }, + minTime: { + type: String, + default: '00:00:00' + // 默认时间优先级:minTime > initTime + }, + maxTime: { + type: String, + default: '23:59:59' + }, +} as const; + + +export type TimePickerProps = ExtractPropTypes; diff --git a/devui/time-picker/src/time-picker.scss b/devui/time-picker/src/time-picker.scss new file mode 100644 index 00000000..6e198a2a --- /dev/null +++ b/devui/time-picker/src/time-picker.scss @@ -0,0 +1,117 @@ +@import '../../style/theme/color'; +@import '../../style/theme/shadow'; +@import '../../style/theme/corner'; +@import '../../style/core/_font'; + +.d-time-picker { + .time-input { + cursor: pointer; + } + + .input-disabled { + cursor: not-allowed; + } + + .time-popup { + // width: 210px ; + height: 310px; + position: fixed; + z-index: -1; + background-color: #ffffff; + box-shadow: $devui-shadow-length-connected-overlay; + visibility: hidden; + opacity: 0; + transition: 0.4s; + -webkit-transition: 0.4s; + transition-property: visibility, opacity, z-index; + + .popup-list { + height: 250px; + display: flex; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + + .popup-item { + width: 33.333%; + height: 244px; + overflow: hidden; + cursor: pointer; + padding: 0; + margin: 2px auto; + padding-right: 8px; + border-right: 1px solid rgba(0, 0, 0, 0.2); + border-top: 1px solid rgba(0, 0, 0, 0); + scroll-behavior: smooth; + + li { + padding: 0; + display: block; + height: 30px; + line-height: 30px; + text-align: center; + width: calc(100% + 8px); + } + + li:last-child { + margin-bottom: 220px; + } + + .active-li { + background-color: #5e7ce0; + color: #ffffff; + } + + .disabled-li { + background-color: $devui-disabled-bg; + color: $devui-disabled-text; + cursor: not-allowed; + } + } + + .popup-item:last-child { + border-right: none; + } + + .popup-item:hover { + overflow-y: auto; + padding-right: 0; + } + } + } + + .show-popup { + visibility: visible; + z-index: 1052; + opacity: 1; + transition-property: visibility, opacity, z-index; + } + + .popup-btn { + height: 60px; + padding: 0 10px; + display: flex; + justify-content: space-between; + align-items: center; + + .ok-btn { + width: 80px; + height: 26px; + line-height: 26px; + text-align: center; + background-color: #5e7ce0; + font-size: 14px; + color: #ffffff; + border-radius: 2px; + cursor: pointer; + } + + .slot-time { + font-size: $devui-font-size-card-title; + cursor: pointer; + } + + .slot-time:hover { + color: $devui-primary-hover; + text-decoration: underline; + } + } +} diff --git a/devui/time-picker/src/time-picker.tsx b/devui/time-picker/src/time-picker.tsx new file mode 100644 index 00000000..327080f1 --- /dev/null +++ b/devui/time-picker/src/time-picker.tsx @@ -0,0 +1,393 @@ +import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' +import { addHourDataFun, addMinuteSecondFun, getPositionFun, getAccordIndex } from './utils' +import { TimePickerProps, timePickerProps } from './time-picker-types' +import './time-picker.scss' +export default defineComponent({ + name: 'DTimepicker', + props: timePickerProps, + emits: ['selectedTimeChage'], + + setup(props: TimePickerProps, ctx) { + const timeVal = ref('') + const inputDom = ref(null) + const popupDom = ref(null) + let hoursList = reactive([]) + let minutesList = reactive([]) + let secondsList = reactive([]) + const activeHour = ref('00') + const activeMinute = ref('00') + const activeSecond = ref('00') + const top = ref(-1000) + const left = ref(-1000) + const showPopup = ref(false) + const firsOpen = ref(true) + + type timeItem = { + isActive: boolean + hour: string + minute: string + second: string + flag: string + isDisabled: boolean + } + type positionPopup = { + top: number + left: number + } + + type timeType = 'hh' | 'HH' | 'mm' | 'MM' | 'ss' | 'SS'; + + type TimeObj = { + time: string + type?: timeType + } + + hoursList = addHourDataFun() + minutesList = addMinuteSecondFun().minutesList + secondsList = addMinuteSecondFun().secondsList + + const inputFocusFun = (e) => { + setTimeHorizon(props.minTime, props.maxTime) + setPostionFun() + if (firsOpen.value) { + initTimeFun(popupDom.value, props.initTime) + } else { + initTimeFun(popupDom.value, timeVal.value) + } + + showPopup.value = true + } + + // 设置默认选中状态 + const initTimeFun = (e, timeString: any) => { + const timeArr = timeString.split(':') + if (firsOpen.value) { + activeHour.value = timeArr[0] + activeMinute.value = timeArr[1] + activeSecond.value = timeArr[2] + firsOpen.value = false + } + + hoursList.map((hourItme: timeItem, hourIndex: number) => { + hourItme.isActive = false + if (activeHour.value == hourItme.hour) { + hourItme.isActive = true + e.firstChild.childNodes[0].scrollTop = hourIndex * 30 + } + }) + minutesList.map((minuteItme: timeItem, minuteIndex: number) => { + minuteItme.isActive = false + if (activeMinute.value == minuteItme.minute) { + minuteItme.isActive = true + e.firstChild.childNodes[1].scrollTop = minuteIndex * 30 + } + }) + secondsList.map((secondItme: timeItem, secondIndex: number) => { + secondItme.isActive = false + if (activeSecond.value == secondItme.second) { + secondItme.isActive = true + e.firstChild.childNodes[2].scrollTop = secondIndex * 30 + } + }) + } + + // 选择时间 + const activeTimeFun = (e: any, item: timeItem, index: number) => { + e.stopPropagation() + if (item.flag == 'hour' && !item.isDisabled) { + hoursList.map((hourItme: timeItem, hourIndex: number) => { + hourItme.isActive = false + }) + hoursList[index].isActive = true + activeHour.value = hoursList[index].hour + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + setTimeMinMax(hoursList[index].hour, 'hh') + } else if (item.flag == 'minute' && !item.isDisabled) { + minutesList.map((minuteItme: timeItem, minuteIndex: number) => { + minuteItme.isActive = false + }) + minutesList[index].isActive = true + activeMinute.value = minutesList[index].minute + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + setTimeMinMax(activeMinute.value, 'mm') + } else if (item.flag == 'second' && !item.isDisabled) { + secondsList.map((secondItme: timeItem, secondIndex: number) => { + secondItme.isActive = false + }) + secondsList[index].isActive = true + activeSecond.value = secondsList[index].second + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + } + + e.target.parentElement.scrollTop = index * 30 + + // e.target.scrollIntoView({behavior:'smooth'}) // scrollIntoView 方法 如果两个时间点击速度过快,会出现滚动不到顶部问题(暂时通过 scrollTop + css实现) + } + + // 关闭弹窗 + const cloasePopupFun = (e: any) => { + e.stopPropagation() + const path = (e.composedPath && e.composedPath()) || e.path + if (e.target == inputDom.value && path.indexOf(popupDom.value) == -1 || e.target.className == 'popup-btn') { + return false + } else if (e.target.className == 'ok-btn') { + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + selectedTimeChage() + showPopup.value = false + } else if (e.target.className == 'slot-bottom') { + showPopup.value = false + selectedTimeChage() + } + else { + showPopup.value = false + } + } + // 初始化 弹窗位置 + const setPostionFun = () => { + getPositionFun(inputDom, (res: positionPopup) => { + top.value = res.top + left.value = res.left + }) + } + + // 获取 和 设置时间范围 + const setTimeHorizon = (minTime: any, maxTime: any) => { + const minTimeArr = minTime.split(':') + const minHour = minTimeArr[0] + const minMinute = minTimeArr[1] + const minSecond = minTimeArr[2] + const minHourLength = getAccordIndex(hoursList, 'hour', minHour) + const minMinuteLength = getAccordIndex(minutesList, 'minute', minMinute) + const minsecondLength = getAccordIndex(secondsList, 'second', minSecond) + + + const maxTimeArr = maxTime.split(':') + const maxHour = maxTimeArr[0] + const maxMinute = maxTimeArr[1] + const maxSecond = maxTimeArr[2] + const maxHourLength = getAccordIndex(hoursList, 'hour', maxHour) + const maxMinuteLength = getAccordIndex(minutesList, 'minute', maxMinute) + const maxsecondLength = getAccordIndex(secondsList, 'second', maxSecond) + + setMinTime(hoursList, minHourLength) + setMinTime(minutesList, minMinuteLength) + setMinTime(secondsList, minsecondLength) + + + setMaxTime(hoursList, maxHourLength) + + + if (minTime != '00:00:00') { + initTimeFun(popupDom.value, minTime) + } + } + + // 设置最小时间 和 最大时间 + const setTimeMinMax = (activeTime: string, type: string) => { + + const minTimeArr = (props.minTime as string).split(':') + const minHour = minTimeArr[0] + const minMinute = minTimeArr[1] + const minSecond = minTimeArr[2] + + const maxTimeArr = (props.maxTime as string).split(':') + const maxHour = maxTimeArr[0] + const maxMinute = maxTimeArr[1] + const maxSecond = maxTimeArr[2] + + if (props.minTime >= timeVal.value) { + activeHour.value = minHour + activeMinute.value = minMinute + activeSecond.value = minSecond + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + initTimeFun(popupDom.value, timeVal.value) + setTimeHorizon(props.minTime, props.maxTime) + setMinTime(minutesList, getAccordIndex(minutesList, 'minute', minMinute)) + setMinTime(secondsList, getAccordIndex(secondsList, 'second', minSecond)) + return + } else if (props.maxTime < timeVal.value) { + activeHour.value = maxHour + activeMinute.value = maxMinute + activeSecond.value = maxSecond + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + initTimeFun(popupDom.value, timeVal.value) + setMaxTime(minutesList, getAccordIndex(minutesList, 'minute', maxMinute)) + setMaxTime(secondsList, getAccordIndex(secondsList, 'second', maxSecond)) + return + } + + if (minHour >= activeHour.value) { + + if (type == 'hh' && minHour == activeTime) { + setMinTime(minutesList, getAccordIndex(minutesList, 'minute', minMinute)) + } else if (type == 'hh' && minHour > activeTime) { + setMinTime(minutesList, minutesList.length) + } else if (type == 'hh' && minHour < activeTime) { + setMinTime(minutesList, 0) + } else if (type == 'mm' && minMinute == activeTime) { + setMinTime(secondsList, getAccordIndex(secondsList, 'second', minSecond)) + } else if (type == 'mm' && minMinute > activeTime) { + setMinTime(secondsList, secondsList.length) + } else if (type == 'mm' && minMinute < activeTime) { + setMinTime(secondsList, 0) + } + } else if (maxHour == activeHour.value) { + console.log(maxMinute, maxHour, activeTime) + if (type == 'hh' && maxHour == activeTime) { + console.log(1) + setMinTime(minutesList, 0) + setMinTime(secondsList, 0) + setMaxTime(minutesList, getAccordIndex(minutesList, 'minute', maxMinute)) + } else if (type == 'mm' && maxMinute == activeTime) { + setMaxTime(secondsList, getAccordIndex(secondsList, 'second', maxSecond)) + } else if (type == 'mm' && maxMinute > activeTime) { + console.log(4) + setMinTime(secondsList, 0) + setMaxTime(secondsList, minutesList.length) + } + } else { + if (activeHour.value < maxHour) { + setMinTime(minutesList, 0) + setMinTime(secondsList, 0) + } else { + setMaxTime(minutesList, minutesList.length) + setMaxTime(secondsList, minutesList.length) + } + } + } + // 设置最小时间 + const setMinTime = (arr: Array, length: number) => { + for (let i = 0; i < arr.length; i++) { + arr[i].isDisabled = false + } + if (length > 0) { + for (let i = 0; i < length; i++) { + arr[i].isDisabled = true + } + } + + } + + // 设置最大时间 + const setMaxTime = (arr: Array, length: number) => { + arr.map((item, index) => { + if (index > length) { + item.isDisabled = true + } + }) + } + + // selectedTimeChage 事件 + const selectedTimeChage = () => { + ctx.emit('selectedTimeChage', timeVal.value) + } + + // slot -- 选择时间 + const chooseTime = (slotTime: TimeObj) => { + if (slotTime.type) { + if (slotTime.type.toLowerCase() == 'hh') { + activeHour.value = slotTime.time + } else if (slotTime.type.toLowerCase() == 'mm') { + activeMinute.value = slotTime.time + } else if (slotTime.type.toLowerCase() == 'ss') { + activeSecond.value = slotTime.time + } + timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value + } else { + const timeArr = slotTime.time.split(':') + activeHour.value = timeArr[0] + activeMinute.value = timeArr[1] + activeSecond.value = timeArr[2] + timeVal.value = slotTime.time + } + // initTimeFun(popupDom.value,timeVal.value) + } + + + // 区分浏览器内核,解决firefox中鼠标离开元素不继续滚动的问题 + const selectTimeFun = (e: MouseEvent) => { + e.stopPropagation() + const ua = navigator.userAgent + if (ua.indexOf('Firefox') > -1) { + initTimeFun(popupDom.value, timeVal.value) + } + } + + onMounted(() => { + // console.log(ctx) + document.addEventListener('scroll', setPostionFun) + window.addEventListener('resize', setPostionFun) + document.addEventListener('click', cloasePopupFun) + }) + + onUnmounted(() => { + document.removeEventListener('scroll', setPostionFun) + window.removeEventListener('resize', setPostionFun) + document.removeEventListener('click', cloasePopupFun) + }) + + + // 暴露子组件方法,用于slot时使用 + ctx.expose({ + chooseTime + }) + return () => { + return ( + <> +
+ +
+ + +
+
+ + + ) + } + + } +}) diff --git a/devui/time-picker/src/utils.ts b/devui/time-picker/src/utils.ts new file mode 100644 index 00000000..7d87b6fc --- /dev/null +++ b/devui/time-picker/src/utils.ts @@ -0,0 +1,68 @@ +/** + * 填充数据 -- 小时 + */ +export function addHourDataFun(){ + const hoursList=[] + for (let i = 0; i < 24; i++) { + hoursList.push({ + hour:i<10?'0'+i:i+'', + isActive:false, + flag:'hour', + isDisabled:false + }) + } + return hoursList +} + +/** + * 填充数据 -- 分钟 && 秒 + */ +export function addMinuteSecondFun(){ + const minutesList=[] + const secondsList=[] + for (let i = 0; i < 60; i++) { + minutesList.push({ + minute:i<10?'0'+i:i+'', + isActive:false, + flag:'minute', + isDisabled:false + }) + secondsList.push({ + second:i<10?'0'+i:i+'', + isActive:false, + flag:'second', + isDisabled:false + }) + } + return {minutesList,secondsList} +} + +/** + * 控制弹窗展示位置 + */ +export function getPositionFun(el,callback){ + const inputDom = el.value.getBoundingClientRect() + let left,top + const button = window.innerHeight - (inputDom.top + inputDom.height) + if(button > inputDom.top + inputDom.height){ + left = inputDom.x + top = inputDom.top + inputDom.height + 10 + }else{ + left = inputDom.x + top = inputDom.top - 320 + } + callback({left,top}) +} + +/** + * 判断对象集合指定属性 符合条件的 数组下标 + */ + export function getAccordIndex(arr:Array,key:string,val:string|number):number{ + const accordIndex =arr.findIndex((item,index)=>{ + if(item[key]== val){ + return index + } + }) + return accordIndex + } + diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index b982c8de..9b783cfe 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -64,7 +64,7 @@ const sidebar = { { text: 'Switch 开关', link: '/components/switch/', status: '已完成' }, { text: 'TagInput 标签输入', link: '/components/tag-input/', status: '已完成' }, { text: 'Textarea 多行文本框', link: '/components/textarea/' }, - { text: 'TimePicker 时间选择器', link: '/components/time-picker/' }, + { text: 'TimePicker 时间选择器', link: '/components/time-picker/', status: '开发中' }, { text: 'Transfer 穿梭框', link: '/components/transfer/' }, { text: 'TreeSelect 树形选择框', link: '/components/tree-select/' }, { text: 'Upload 上传', link: '/components/upload/', status: '开发中' }, diff --git a/sites/components/time-picker/index.md b/sites/components/time-picker/index.md new file mode 100644 index 00000000..10d59f68 --- /dev/null +++ b/sites/components/time-picker/index.md @@ -0,0 +1,176 @@ +# TimePicker组件 + +时间选择器。 + +### 何时使用 + +当用户需要输入一个时间,可以点击标准输入框,弹出面板进行选择 + +### 基本用法 +:::demo +```vue + + + +``` +::: + +### 最大值和最小值 +:::demo +```vue + + +``` +::: + +### 传入模板 +:::demo +```vue + + + + + +``` +::: + +### 事件 +:::demo +```vue + + +``` +::: + + +## TimePicker参数 + + |参数|类型|默认|说明|进度| + |----|----|----|----|----| + |disabled| boolean |false|可选,禁用选择|基础版暂无样式| + |timePickerWidth / time-picker-width |number|---|----|基础版已完成| + |format |string|'hh:mm:ss'|可选,传入格式化,控制时间格式|基础版暂未开始| + |minTime / min-time|string|'00:00:00'|可选,限制最小可选时间|基础版已完成| + |maxTime / max-time|string|'23:59:59'|可选,限制最大可选时间|基本版已完成| + |customViewTemplate|TemplateRef|--|可选,自定义快捷设置时间或自定义操作区内容|基础版已完成| + +## TimePicker事件 + |参数|类型|说明|进度| + |----|----|----|----| + |selectedTimeChange|EventEmitter|可选,确定的时候会发出新激活的子项的数据|基础版已完成| + -- Gitee From d2ccd1e18ae821a63c6a96de8722e863e1c42118 Mon Sep 17 00:00:00 2001 From: Dreamer <154239735@qq.com> Date: Sun, 19 Sep 2021 20:14:59 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E4=B8=8D=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E8=A7=84=E5=AE=9A=E7=9A=84=E6=A0=B7=E5=BC=8F=E7=B1=BB?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/time-picker/src/time-picker.scss | 2 +- devui/time-picker/src/time-picker.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devui/time-picker/src/time-picker.scss b/devui/time-picker/src/time-picker.scss index 6e198a2a..ab74375f 100644 --- a/devui/time-picker/src/time-picker.scss +++ b/devui/time-picker/src/time-picker.scss @@ -3,7 +3,7 @@ @import '../../style/theme/corner'; @import '../../style/core/_font'; -.d-time-picker { +.devui-time-picker { .time-input { cursor: pointer; } diff --git a/devui/time-picker/src/time-picker.tsx b/devui/time-picker/src/time-picker.tsx index 327080f1..7e63e45f 100644 --- a/devui/time-picker/src/time-picker.tsx +++ b/devui/time-picker/src/time-picker.tsx @@ -334,7 +334,7 @@ export default defineComponent({ return () => { return ( <> -
+
Date: Tue, 5 Oct 2021 22:09:18 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=8F=90=E4=BA=A4?= =?UTF-8?q?timePicer=E6=8F=90=E5=8F=8A=E5=88=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../popup-line/composables/use-popue-line.ts | 260 +++++++++++ .../src/components/popup-line/index.scss | 70 +++ .../src/components/popup-line/index.tsx | 135 ++++++ .../src/components/time-popup/index.scss | 54 +++ .../src/components/time-popup/index.tsx | 114 +++++ .../time-picker/src/composables/use-popup.ts | 148 ++++++ devui/time-picker/src/time-picker-types.ts | 58 ++- devui/time-picker/src/time-picker.scss | 130 ++---- devui/time-picker/src/time-picker.tsx | 436 ++++-------------- devui/time-picker/src/types.ts | 14 + devui/time-picker/src/utils.ts | 140 +++--- sites/components/time-picker/index.md | 108 +++-- 12 files changed, 1103 insertions(+), 564 deletions(-) create mode 100644 devui/time-picker/src/components/popup-line/composables/use-popue-line.ts create mode 100644 devui/time-picker/src/components/popup-line/index.scss create mode 100644 devui/time-picker/src/components/popup-line/index.tsx create mode 100644 devui/time-picker/src/components/time-popup/index.scss create mode 100644 devui/time-picker/src/components/time-popup/index.tsx create mode 100644 devui/time-picker/src/composables/use-popup.ts create mode 100644 devui/time-picker/src/types.ts diff --git a/devui/time-picker/src/components/popup-line/composables/use-popue-line.ts b/devui/time-picker/src/components/popup-line/composables/use-popue-line.ts new file mode 100644 index 00000000..6a1b1e76 --- /dev/null +++ b/devui/time-picker/src/components/popup-line/composables/use-popue-line.ts @@ -0,0 +1,260 @@ +import { Ref, ref } from 'vue' +import { ArrType } from '../../../types' + +const usePopupLine = ( + hourListRef: Array, minuteListRef: Array, secondListRef: Array, + minTime: string, maxTime: string, format: string, timeListDom: Ref, +): any => { + + const activeTime = ref('00:00:00') + const activeHour = ref('00') + const activeMinute = ref('00') + const activeSecond = ref('00') + + const activeTimeFun = (e: any, item: ArrType, index: number) => { + if (item.isDisabled) { + return false + } else { + setTimeActive(item, index) + e.target.parentElement.scrollTop = index * 30 + } + } + + + const setTimeActive = (item: ArrType, index: number) => { + + let activeTimeList = [] + let acitveTimeValue = ref('') + if (item.flag == 'hour') { + activeTimeList = hourListRef + acitveTimeValue = activeHour + getItemAstrict(item) + } else if (item.flag == 'minute') { + activeTimeList = minuteListRef + acitveTimeValue = activeMinute + getItemAstrict(item) + } else if (item.flag == 'second') { + activeTimeList = secondListRef + acitveTimeValue = activeSecond + } + + activeTimeList.map((tiemItem) => { + tiemItem.isActive = false + }) + activeTimeList[index].isActive = true + acitveTimeValue.value = activeTimeList[index].time + + + activeTime.value = `${activeHour.value}:${activeMinute.value}:${activeSecond.value}` + + if (activeTime.value < minTime) { + activeTime.value = minTime + resetTimeValue(minTime) + } else if (format == 'mm:ss' && `${activeMinute.value}:${activeSecond.value}` > maxTime.slice(3)) { + const newMinTime = minTime.slice(0, 3) + maxTime.slice(3) + resetTimeValue(newMinTime) + } else if (activeTime.value > maxTime) { + activeTime.value = maxTime + resetTimeValue(maxTime) + } + } + + // 获取最大值 最小值 + const getItemAstrict = (item: ArrType): void => { + let min = '00' + let max = '00' + + const minTimeHour = minTime.split(':')[0] + const minTimeMinute = minTime.split(':')[1] + const minTimeSecond = minTime.split(':')[2] + + const maxTimeHour = maxTime.split(':')[0] + const maxTimeMinute = maxTime.split(':')[1] + const maxTimeSecond = maxTime.split(':')[2] + + if (item.flag == 'hour') { + if (item.time == minTimeHour) { + min = minTimeMinute + // console.log('11') + setItemAstrict(minuteListRef, min, max) + activeMinute.value < minTimeMinute && setItemAstrict(secondListRef, minTimeSecond, max) + } else if (item.time == maxTimeHour) { + max = maxTimeMinute + // console.log('22') + setItemAstrict(minuteListRef, min, max) + setItemAstrict(secondListRef, min, maxTimeSecond) + } else { + // console.log('33') + setItemAstrict(minuteListRef, min, max) + setItemAstrict(secondListRef, min, max) + } + } + if (item.flag == 'minute' && format == 'mm:ss') { + if (item.time == minTimeMinute) { + min = minTimeSecond + setItemAstrict(secondListRef, min, max) + } else if (item.time == maxTimeMinute) { + max = maxTimeSecond + setItemAstrict(secondListRef, min, max) + } else { + setItemAstrict(secondListRef, min, max) + } + } else if (item.flag == 'minute') { + if (activeHour.value == minTimeHour && item.time == minTimeMinute) { + min = minTimeSecond + setItemAstrict(secondListRef, min, max) + // console.log('44') + } else if (activeHour.value == maxTimeHour && item.time == maxTimeMinute) { + max = maxTimeSecond + // console.log('55') + setItemAstrict(secondListRef, min, max) + } else { + // console.log('66') + setItemAstrict(secondListRef, min, max) + } + } + + } + // 设置最大值 最小值 + const setItemAstrict = (timeArr: Array, min: string, max: string) => { + timeArr.map(itme => { + if (min != '00' && itme.time < min) { + itme.isDisabled = true + } else if (max != '00' && itme.time > max) { + itme.isDisabled = true + } else { + itme.isDisabled = false + } + }) + } + + // 指定时间 + const resetTimeValue = (time: string) => { + const timeValueArr = time.split(':') + const minTiveArr = minTime.split(':') + + let hh = 0 + let mm = 0 + let ss = 0 + + if (format == 'hh:mm:ss') { + + hh = parseInt(timeValueArr[0]) + mm = parseInt(timeValueArr[1]) + ss = parseInt(timeValueArr[2]) + + timeListDom.value.children[0].scrollTop = hh * 30 + timeListDom.value.children[1].scrollTop = mm * 30 + timeListDom.value.children[2].scrollTop = ss * 30 + + activeHour.value = timeValueArr[0] + activeMinute.value = timeValueArr[1] + activeSecond.value = timeValueArr[2] + + resetTimeActive(hourListRef, timeValueArr[0]) + resetTimeActive(minuteListRef, timeValueArr[1]) + resetTimeActive(secondListRef, timeValueArr[2]) + + resetTimeAstrict(hourListRef, activeHour.value) + resetTimeAstrict(minuteListRef, activeMinute.value) + + } + else if (format == 'mm:hh:ss') { + hh = parseInt(timeValueArr[0]) + mm = parseInt(timeValueArr[1]) + ss = parseInt(timeValueArr[2]) + + timeListDom.value.children[0].scrollTop = mm * 30 + timeListDom.value.children[1].scrollTop = hh * 30 + timeListDom.value.children[2].scrollTop = ss * 30 + + activeHour.value = timeValueArr[0] + activeMinute.value = timeValueArr[1] + activeSecond.value = timeValueArr[2] + + resetTimeActive(hourListRef, timeValueArr[0]) + resetTimeActive(minuteListRef, timeValueArr[1]) + resetTimeActive(secondListRef, timeValueArr[2]) + + resetTimeAstrict(hourListRef, activeHour.value) + resetTimeAstrict(minuteListRef, activeMinute.value) + + } else if (format == 'hh:mm') { + + hh = parseInt(timeValueArr[0]) + mm = parseInt(timeValueArr[1]) + + timeListDom.value.children[0].scrollTop = hh * 30 + timeListDom.value.children[1].scrollTop = mm * 30 + + activeHour.value = timeValueArr[0] + activeMinute.value = timeValueArr[1] + + resetTimeActive(hourListRef, timeValueArr[0]) + resetTimeActive(minuteListRef, timeValueArr[1]) + + resetTimeAstrict(hourListRef, activeHour.value) + + } else if (format == 'mm:ss') { + + mm = parseInt(timeValueArr[1]) + ss = parseInt(timeValueArr[2]) + + timeListDom.value.children[0].scrollTop = mm * 30 + timeListDom.value.children[1].scrollTop = ss * 30 + + activeHour.value = minTiveArr[0] + activeMinute.value = timeValueArr[1] + activeSecond.value = timeValueArr[2] + + resetTimeActive(minuteListRef, timeValueArr[1]) + resetTimeActive(secondListRef, timeValueArr[2]) + + resetTimeAstrict(minuteListRef, activeMinute.value) + } + // activeTime.value = `${activeHour.value}:${activeMinute.value}:${activeSecond.value}` + + } + + // 解决清空之后,再次打开 最大值最小值限制范围失效 + const resetTimeAstrict = (timeArr: Array, time: string) => { + timeArr.map(item => { + if (item.time == time) { + getItemAstrict(item) + } + }) + } + + // 指定选中 + const resetTimeActive = (timeArr: Array, itemValue: string) => { + timeArr.map(item => { + if (item.time == itemValue) { + item.isActive = true + } else { + item.isActive = false + } + }) + } + + // 暂时返回选中 时 分 秒 + const getNewTime = () => { + return { activeTime, activeHour, activeMinute, activeSecond } + } + + return { + activeTime, + activeHour, + activeMinute, + activeSecond, + activeTimeFun, + resetTimeValue, + getNewTime + } +} + +export { + usePopupLine +} + + + diff --git a/devui/time-picker/src/components/popup-line/index.scss b/devui/time-picker/src/components/popup-line/index.scss new file mode 100644 index 00000000..b39d6ad2 --- /dev/null +++ b/devui/time-picker/src/components/popup-line/index.scss @@ -0,0 +1,70 @@ +@import '../../../../style/theme/color'; +@import '../../../../style/theme/shadow'; +@import '../../../../style/theme/corner'; +@import '../../../../style/core/_font'; + +.time-list { + width: 100%; + height: 250px; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + border-top: 1px solid rgba(0, 0, 0, 0.2); + + .time-ul { + height: 244px; + display: inline-block; + overflow: hidden; + cursor: pointer; + padding: 0; + margin: 0 auto; + padding-right: 8px; + border-right: 1px solid rgba(0, 0, 0, 0.2); + scroll-behavior: smooth; + padding-top: 2px; + border-top: 1px solid rgba(0, 0, 0, 0); + + &:hover { + overflow-y: overlay; + // overflow-y:auto ; + // padding-right: 0; + } + + &:last-child { + border-right: none; + } + + li { + padding: 0; + // text-align: center; + // padding-left: 40px; + text-align: center; + height: 30px; + line-height: 30px; + width: calc(100% + 8px); + + &:last-child { + margin-bottom: 214px; + } + + &:hover { + background-color: #5d7ef80c; + color: #8aa2f0; + } + } + + .active-li { + background-color: #5e7ce0; + color: #ffffff; + + &:hover { + background-color: #5e7ce0; + color: #ffffff; + } + } + + .disabled-li { + background-color: $devui-disabled-bg; + color: $devui-disabled-text; + cursor: not-allowed; + } + } +} diff --git a/devui/time-picker/src/components/popup-line/index.tsx b/devui/time-picker/src/components/popup-line/index.tsx new file mode 100644 index 00000000..b617e39a --- /dev/null +++ b/devui/time-picker/src/components/popup-line/index.tsx @@ -0,0 +1,135 @@ +import { ref, defineComponent } from 'vue' +import { usePopupLine } from './composables/use-popue-line' +import './index.scss' + +type ArrType = { + type: 'hour' | 'minute' | 'seconde' + isActive: boolean + isDisabled: boolean + time: string + flag: string +} + +export default defineComponent({ + name: 'TTimeList', + props: { + hourList: { + type: Array, + default: () => [] + }, + minuteList: { + type: Array, + default: () => [] + }, + secondList: { + type: Array, + default: () => [] + }, + format: { + type: String, + default: 'hh:mm:ss' + }, + minTime: { + type: String, + default: '00:00:00' + }, + maxTime: { + type: String, + default: '23:59:59' + } + }, + setup(props, ctx,) { + const timeListDom = ref() + const { + getNewTime, + activeTimeFun, + resetTimeValue + } = usePopupLine( + props.hourList as Array, + props.minuteList as Array, + props.secondList as Array, + props.minTime, + props.maxTime, + props.format, + timeListDom, + ) + + const restScrooTop = () => { + for (let i = 0; i < timeListDom.value.children.length; i++) { + timeListDom.value.children[i].scrollTop = 0 + } + } + + const setOutoTime = (time: string) => { + resetTimeValue(time) + } + + + + const TimeLi = (timeArr: Array): any => { + return ( + timeArr.map((item: ArrType, index: number) => { + return ( +
  • { activeTimeFun(e, item, index,) }} + >{item.time} +
  • + ) + }) + ) + } + + const TimeUl = (timeList: Array) => { + return ( +
      6 ? '33.33%' : '50%' }}> {TimeLi(timeList)}
    + ) + } + + const formatTimeUl = () => { + const timeList = { + 'hh': props.hourList, + 'mm': props.minuteList, + 'ss': props.secondList + } + + const timeFormatArr = (props.format as string).split(':') + + return ( + timeFormatArr.map((timeItme) => { + return ( + TimeUl(timeList[timeItme]) + ) + }) + ) + } + + //TODO: 区分浏览器内核,解决firefox中鼠标离开元素不继续滚动的情况 + const selectTimeFun = (e: MouseEvent) => { + e.stopPropagation() + console.log(e); + + // e.stopPropagation() + // const ua = navigator.userAgent + // if (ua.indexOf('Firefox') > -1) { + // resetTimeValue(activeTime) + // } + } + + ctx.expose({ + restScrooTop, setOutoTime, getNewTime + }) + + return () => { + return ( +
    + { + formatTimeUl() + } +
    + ) + } + } +}) + + diff --git a/devui/time-picker/src/components/time-popup/index.scss b/devui/time-picker/src/components/time-popup/index.scss new file mode 100644 index 00000000..e8e2b6fc --- /dev/null +++ b/devui/time-picker/src/components/time-popup/index.scss @@ -0,0 +1,54 @@ +@import '../../../../style/theme/color'; +@import '../../../../style/theme/shadow'; +@import '../../../../style/theme/corner'; +@import '../../../../style/core/_font'; + +.time-popup { + height: 310px; + position: fixed; + z-index: -1; + background-color: #ffffff; + box-shadow: $devui-shadow-length-connected-overlay; + visibility: hidden; + opacity: 0; + transition: 0.4s; + -webkit-transition: 0.4s; + transition-property: visibility, opacity, z-index; +} + +.show-popup { + visibility: visible; + z-index: 1052; + opacity: 1; + transition-property: visibility, opacity, z-index; +} + +.popup-btn { + height: 60px; + padding: 0 10px; + display: flex; + justify-content: space-between; + align-items: center; + + .ok-btn { + width: 80px; + height: 26px; + line-height: 26px; + text-align: center; + background-color: #5e7ce0; + font-size: 14px; + color: #ffffff; + border-radius: 2px; + cursor: pointer; + } + + .slot-time { + font-size: $devui-font-size-card-title; + cursor: pointer; + } + + .slot-time:hover { + color: $devui-primary-hover; + text-decoration: underline; + } +} diff --git a/devui/time-picker/src/components/time-popup/index.tsx b/devui/time-picker/src/components/time-popup/index.tsx new file mode 100644 index 00000000..304d4fd3 --- /dev/null +++ b/devui/time-picker/src/components/time-popup/index.tsx @@ -0,0 +1,114 @@ +import { defineComponent, ref, watch, onMounted } from 'vue'; +import { initializeTimeData, setTimeAstrict } from '../../utils' +import TimeList from '../popup-line/index' +import { Button } from '../../../../button/index'; +import { ArrType } from '../../types' + +import './index.scss' +export default defineComponent({ + name: 'TTimePopup', + components: { + TimeList, Button + }, + props: { + showPopup: { + type: Boolean, + default: false + }, + popupTop: { + type: Number, + default: -100 + }, + popupLeft: { + type: Number, + default: -100 + }, + popupWidth: { + type: Number, + default: 300 + }, + popupFormat: { + type: String, + default: 'hh:mm:ss' + }, + minTime: { + type: String, + default: '00:00:00' + }, + maxTime: { + type: String, + default: '23:59:59' + }, + bindData: { + type: String, + default: '00:00:00' + } + }, + emits: ['subData'], + setup(props, ctx) { + const popupDome = ref() + const timeListDom = ref() + const hourList = initializeTimeData('hour') + const minuteList = initializeTimeData('minute') + const secondList = initializeTimeData('second') + + onMounted(() => { + setTimeAstrict(hourList, minuteList, secondList, props.minTime, props.maxTime, props.popupFormat) + }) + + watch(() => [props.showPopup, props.bindData], ([showPopup, newTimeVal], [oldPopup, oldTimeVal]) => { + + if (showPopup || newTimeVal != oldTimeVal) { + timeListDom.value.setOutoTime(newTimeVal) + } else { + timeListDom.value.restScrooTop() + } + }) + + const changTimeData = () => { + return timeListDom.value.getNewTime() + } + + const subDataFun = (e: MouseEvent) => { + e.stopPropagation() + ctx.emit('subData') + } + + ctx.expose({ + changTimeData + }) + + return () => { + return ( + <> +
    + + + +
    + + ) + } + } +}) + diff --git a/devui/time-picker/src/composables/use-popup.ts b/devui/time-picker/src/composables/use-popup.ts new file mode 100644 index 00000000..74e7742e --- /dev/null +++ b/devui/time-picker/src/composables/use-popup.ts @@ -0,0 +1,148 @@ +import { Ref, ref } from 'vue' +import { TimeObj } from '../types' +import { getPositionFun } from '../utils' + +export default function usePicker( + hh:Ref,mm:Ref,ss:Ref,minTime:string,format:string, + autoOpen:boolean,disabled:boolean,value:string + ):any{ + const isActive = ref(false) + const showPopup = ref(false) + const devuiTimePicker = ref() + const inputDom = ref() + const left = ref(-100) + const top = ref(-100) + const timePopupDom = ref() + const timePickerValue = ref('') + const showClearIcon = ref(false) + const firsthandActiveTime = ref(`${hh.value}:${mm.value}:${ss.value}`) + const vModeValue = ref(value) + + const getPopupPosition = ()=>{ + getPositionFun(devuiTimePicker.value,left,top) + } + + const clickVerifyFun = (e: any) => { + e.stopPropagation() + isActive.value = false + showPopup.value = false + + if(disabled) return + + const path = (e.composedPath && e.composedPath()) || e.path + path.map( item => { + if (item == devuiTimePicker.value) { + if(firsthandActiveTime.value == '00:00:00'){ + vModeValue.value == '' + ? vModeValue.value = '00:00:00' + : '' + + vModeValue.value > minTime + ? firsthandActiveTime.value = vModeValue.value + : firsthandActiveTime.value = minTime + } + setInputValue() + isActive.value = true + showPopup.value = true + } + }) + } + + const getTimeValue = (e:MouseEvent)=>{ + e.stopPropagation() + if(showPopup.value){ + hh.value = timePopupDom.value.changTimeData().activeHour.value + mm.value = timePopupDom.value.changTimeData().activeMinute.value + ss.value = timePopupDom.value.changTimeData().activeSecond.value + firsthandActiveTime.value = `${hh.value}:${mm.value}:${ss.value}` + setInputValue() + } + } + + const setInputValue = ()=> { + if(format == 'hh:mm:ss'){ + vModeValue.value = `${hh.value}:${mm.value}:${ss.value}` + }else if(format == 'mm:hh:ss'){ + vModeValue.value = `${mm.value}:${hh.value}:${ss.value}` + }else if(format == 'hh:mm'){ + vModeValue.value = `${hh.value}:${mm.value}` + }else if(format == 'mm:ss'){ + vModeValue.value = `${mm.value}:${ss.value}` + } + } + + const clearAll = (e:MouseEvent)=>{ + e.stopPropagation() + showPopup.value = false + + if(minTime != '00:00:00'){ + const minTimeArr = minTime.split(':') + hh.value = minTimeArr[0] + mm.value = minTimeArr[1] + ss.value = minTimeArr[2] + }else{ + hh.value = '00' + mm.value = '00' + ss.value = '00' + } + firsthandActiveTime.value = `${hh.value}:${mm.value}:${ss.value}` + setInputValue() + } + + const isOutOpen =()=>{ + if(autoOpen){ + + const timeArr = vModeValue.value.split(':') + hh.value = timeArr[0] + mm.value = timeArr[1] + ss.value = timeArr[2] + + firsthandActiveTime.value = vModeValue.value + setInputValue() + isActive.value = true + showPopup.value = autoOpen + } + } + + // slot -- 选择时间 + const chooseTime = (slotTime:TimeObj) => { + if (slotTime.type) { + if (slotTime.type.toLowerCase() == 'hh') { + hh.value = slotTime.time + } else if (slotTime.type.toLowerCase() == 'mm') { + mm.value = slotTime.time + } else if (slotTime.type.toLowerCase() == 'ss') { + ss.value = slotTime.time + } + firsthandActiveTime.value = `${hh.value}:${mm.value}:${ss.value}` + setInputValue() + } else { + const timeArr = slotTime.time.split(':') + hh.value = timeArr[0] + mm.value = timeArr[1] + ss.value = timeArr[2] + firsthandActiveTime.value = `${hh.value}:${mm.value}:${ss.value}` + setInputValue() + } + } + + return { + isActive, + showPopup, + devuiTimePicker, + timePickerValue, + inputDom, + timePopupDom, + left,top, + showClearIcon, + firsthandActiveTime, + vModeValue, + getPopupPosition, + setInputValue, + getTimeValue, + clickVerifyFun, + isOutOpen, + clearAll, + chooseTime + } +} \ No newline at end of file diff --git a/devui/time-picker/src/time-picker-types.ts b/devui/time-picker/src/time-picker-types.ts index a033c75b..ca0b56c7 100644 --- a/devui/time-picker/src/time-picker-types.ts +++ b/devui/time-picker/src/time-picker-types.ts @@ -1,31 +1,39 @@ import { ExtractPropTypes, PropType } from 'vue'; export const timePickerProps = { - initTime: { - type: String, - default: '00:00:00' - }, - placeholder: { - type: String, - default: '00:00:00' - }, - disabled: { - type: Boolean, - default: false - }, - timePickerWidth: { - type: Number, - default: 210 - }, - minTime: { - type: String, - default: '00:00:00' - // 默认时间优先级:minTime > initTime - }, - maxTime: { - type: String, - default: '23:59:59' - }, + modelValue: { + type: String, + default: '' + }, + placeholder: { + type: String, + default: '00:00:00' + }, + disabled: { + type: Boolean, + default: false + }, + timePickerWidth: { + type: Number, + default: 210 + }, + minTime: { + type: String, + default: '00:00:00' + // 默认时间优先级:minTime > modelValue + }, + maxTime: { + type: String, + default: '23:59:59' + }, + format: { + type: String, + default: 'hh:mm:ss' + }, + autoOpen: { + type: Boolean, + default: false + } } as const; diff --git a/devui/time-picker/src/time-picker.scss b/devui/time-picker/src/time-picker.scss index ab74375f..46cacddf 100644 --- a/devui/time-picker/src/time-picker.scss +++ b/devui/time-picker/src/time-picker.scss @@ -4,114 +4,48 @@ @import '../../style/core/_font'; .devui-time-picker { - .time-input { - cursor: pointer; - } - - .input-disabled { - cursor: not-allowed; + width: 200px; + box-sizing: border-box; + display: flex; + justify-content: space-between; + align-items: center; + border: 1px solid $devui-form-control-line; + position: relative; + transition: 0.2s; + + &:hover { + border: 1px solid $devui-form-control-line-hover; } - .time-popup { - // width: 210px ; - height: 310px; - position: fixed; - z-index: -1; - background-color: #ffffff; - box-shadow: $devui-shadow-length-connected-overlay; - visibility: hidden; - opacity: 0; - transition: 0.4s; - -webkit-transition: 0.4s; - transition-property: visibility, opacity, z-index; - - .popup-list { - height: 250px; - display: flex; - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - - .popup-item { - width: 33.333%; - height: 244px; - overflow: hidden; - cursor: pointer; - padding: 0; - margin: 2px auto; - padding-right: 8px; - border-right: 1px solid rgba(0, 0, 0, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0); - scroll-behavior: smooth; - - li { - padding: 0; - display: block; - height: 30px; - line-height: 30px; - text-align: center; - width: calc(100% + 8px); - } - - li:last-child { - margin-bottom: 220px; - } - - .active-li { - background-color: #5e7ce0; - color: #ffffff; - } - - .disabled-li { - background-color: $devui-disabled-bg; - color: $devui-disabled-text; - cursor: not-allowed; - } - } - - .popup-item:last-child { - border-right: none; - } - - .popup-item:hover { - overflow-y: auto; - padding-right: 0; - } - } - } - - .show-popup { - visibility: visible; - z-index: 1052; - opacity: 1; - transition-property: visibility, opacity, z-index; + .time-input { + border: none; + outline: none; + width: 100%; } - .popup-btn { - height: 60px; - padding: 0 10px; + .time-input-icon { display: flex; justify-content: space-between; align-items: center; + cursor: pointer; - .ok-btn { - width: 80px; - height: 26px; - line-height: 26px; - text-align: center; - background-color: #5e7ce0; - font-size: 14px; - color: #ffffff; - border-radius: 2px; - cursor: pointer; + div { + text-align: right; + padding: 0 4px; } + } +} - .slot-time { - font-size: $devui-font-size-card-title; - cursor: pointer; - } +.time-picker-active { + border: 1px solid $devui-form-control-line-active !important; +} - .slot-time:hover { - color: $devui-primary-hover; - text-decoration: underline; - } +.picker-disabled { + background-color: $devui-unavailable; + cursor: no-drop; + + * { + background-color: $devui-unavailable; + cursor: no-drop; } } diff --git a/devui/time-picker/src/time-picker.tsx b/devui/time-picker/src/time-picker.tsx index 7e63e45f..6b9e85b3 100644 --- a/devui/time-picker/src/time-picker.tsx +++ b/devui/time-picker/src/time-picker.tsx @@ -1,393 +1,119 @@ -import { defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue' -import { addHourDataFun, addMinuteSecondFun, getPositionFun, getAccordIndex } from './utils' +import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue' import { TimePickerProps, timePickerProps } from './time-picker-types' +import { Icon } from '../../icon' +import usePicker from './composables/use-popup' +import TimePopup from './components/time-popup/index' + import './time-picker.scss' + export default defineComponent({ name: 'DTimepicker', + components: { TimePopup }, props: timePickerProps, - emits: ['selectedTimeChage'], - + emits: ['selectedTimeChage', 'update:modelValue'], setup(props: TimePickerProps, ctx) { - const timeVal = ref('') - const inputDom = ref(null) - const popupDom = ref(null) - let hoursList = reactive([]) - let minutesList = reactive([]) - let secondsList = reactive([]) + const activeHour = ref('00') const activeMinute = ref('00') const activeSecond = ref('00') - const top = ref(-1000) - const left = ref(-1000) - const showPopup = ref(false) - const firsOpen = ref(true) - - type timeItem = { - isActive: boolean - hour: string - minute: string - second: string - flag: string - isDisabled: boolean - } - type positionPopup = { - top: number - left: number - } - - type timeType = 'hh' | 'HH' | 'mm' | 'MM' | 'ss' | 'SS'; - - type TimeObj = { - time: string - type?: timeType - } - - hoursList = addHourDataFun() - minutesList = addMinuteSecondFun().minutesList - secondsList = addMinuteSecondFun().secondsList - - const inputFocusFun = (e) => { - setTimeHorizon(props.minTime, props.maxTime) - setPostionFun() - if (firsOpen.value) { - initTimeFun(popupDom.value, props.initTime) - } else { - initTimeFun(popupDom.value, timeVal.value) - } - - showPopup.value = true - } - - // 设置默认选中状态 - const initTimeFun = (e, timeString: any) => { - const timeArr = timeString.split(':') - if (firsOpen.value) { - activeHour.value = timeArr[0] - activeMinute.value = timeArr[1] - activeSecond.value = timeArr[2] - firsOpen.value = false - } - - hoursList.map((hourItme: timeItem, hourIndex: number) => { - hourItme.isActive = false - if (activeHour.value == hourItme.hour) { - hourItme.isActive = true - e.firstChild.childNodes[0].scrollTop = hourIndex * 30 - } - }) - minutesList.map((minuteItme: timeItem, minuteIndex: number) => { - minuteItme.isActive = false - if (activeMinute.value == minuteItme.minute) { - minuteItme.isActive = true - e.firstChild.childNodes[1].scrollTop = minuteIndex * 30 - } - }) - secondsList.map((secondItme: timeItem, secondIndex: number) => { - secondItme.isActive = false - if (activeSecond.value == secondItme.second) { - secondItme.isActive = true - e.firstChild.childNodes[2].scrollTop = secondIndex * 30 - } - }) - } - - // 选择时间 - const activeTimeFun = (e: any, item: timeItem, index: number) => { - e.stopPropagation() - if (item.flag == 'hour' && !item.isDisabled) { - hoursList.map((hourItme: timeItem, hourIndex: number) => { - hourItme.isActive = false - }) - hoursList[index].isActive = true - activeHour.value = hoursList[index].hour - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - setTimeMinMax(hoursList[index].hour, 'hh') - } else if (item.flag == 'minute' && !item.isDisabled) { - minutesList.map((minuteItme: timeItem, minuteIndex: number) => { - minuteItme.isActive = false - }) - minutesList[index].isActive = true - activeMinute.value = minutesList[index].minute - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - setTimeMinMax(activeMinute.value, 'mm') - } else if (item.flag == 'second' && !item.isDisabled) { - secondsList.map((secondItme: timeItem, secondIndex: number) => { - secondItme.isActive = false - }) - secondsList[index].isActive = true - activeSecond.value = secondsList[index].second - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - } - - e.target.parentElement.scrollTop = index * 30 - - // e.target.scrollIntoView({behavior:'smooth'}) // scrollIntoView 方法 如果两个时间点击速度过快,会出现滚动不到顶部问题(暂时通过 scrollTop + css实现) - } - - // 关闭弹窗 - const cloasePopupFun = (e: any) => { - e.stopPropagation() - const path = (e.composedPath && e.composedPath()) || e.path - if (e.target == inputDom.value && path.indexOf(popupDom.value) == -1 || e.target.className == 'popup-btn') { - return false - } else if (e.target.className == 'ok-btn') { - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - selectedTimeChage() - showPopup.value = false - } else if (e.target.className == 'slot-bottom') { - showPopup.value = false - selectedTimeChage() - } - else { - showPopup.value = false - } - } - // 初始化 弹窗位置 - const setPostionFun = () => { - getPositionFun(inputDom, (res: positionPopup) => { - top.value = res.top - left.value = res.left - }) - } - - // 获取 和 设置时间范围 - const setTimeHorizon = (minTime: any, maxTime: any) => { - const minTimeArr = minTime.split(':') - const minHour = minTimeArr[0] - const minMinute = minTimeArr[1] - const minSecond = minTimeArr[2] - const minHourLength = getAccordIndex(hoursList, 'hour', minHour) - const minMinuteLength = getAccordIndex(minutesList, 'minute', minMinute) - const minsecondLength = getAccordIndex(secondsList, 'second', minSecond) - - - const maxTimeArr = maxTime.split(':') - const maxHour = maxTimeArr[0] - const maxMinute = maxTimeArr[1] - const maxSecond = maxTimeArr[2] - const maxHourLength = getAccordIndex(hoursList, 'hour', maxHour) - const maxMinuteLength = getAccordIndex(minutesList, 'minute', maxMinute) - const maxsecondLength = getAccordIndex(secondsList, 'second', maxSecond) - - setMinTime(hoursList, minHourLength) - setMinTime(minutesList, minMinuteLength) - setMinTime(secondsList, minsecondLength) - + const format = props.format.toLowerCase() + + const { + isActive, + showPopup, + devuiTimePicker, + inputDom, + left, top, + showClearIcon, + firsthandActiveTime, + chooseTime, + getTimeValue, + clickVerifyFun, + isOutOpen, + clearAll, + timePopupDom, + vModeValue, + getPopupPosition + } = usePicker(activeHour, activeMinute, activeSecond, props.minTime, format, props.autoOpen, props.disabled, props.modelValue) - setMaxTime(hoursList, maxHourLength) - - if (minTime != '00:00:00') { - initTimeFun(popupDom.value, minTime) - } - } - - // 设置最小时间 和 最大时间 - const setTimeMinMax = (activeTime: string, type: string) => { - - const minTimeArr = (props.minTime as string).split(':') - const minHour = minTimeArr[0] - const minMinute = minTimeArr[1] - const minSecond = minTimeArr[2] - - const maxTimeArr = (props.maxTime as string).split(':') - const maxHour = maxTimeArr[0] - const maxMinute = maxTimeArr[1] - const maxSecond = maxTimeArr[2] - - if (props.minTime >= timeVal.value) { - activeHour.value = minHour - activeMinute.value = minMinute - activeSecond.value = minSecond - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - initTimeFun(popupDom.value, timeVal.value) - setTimeHorizon(props.minTime, props.maxTime) - setMinTime(minutesList, getAccordIndex(minutesList, 'minute', minMinute)) - setMinTime(secondsList, getAccordIndex(secondsList, 'second', minSecond)) - return - } else if (props.maxTime < timeVal.value) { - activeHour.value = maxHour - activeMinute.value = maxMinute - activeSecond.value = maxSecond - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - initTimeFun(popupDom.value, timeVal.value) - setMaxTime(minutesList, getAccordIndex(minutesList, 'minute', maxMinute)) - setMaxTime(secondsList, getAccordIndex(secondsList, 'second', maxSecond)) - return - } - - if (minHour >= activeHour.value) { - - if (type == 'hh' && minHour == activeTime) { - setMinTime(minutesList, getAccordIndex(minutesList, 'minute', minMinute)) - } else if (type == 'hh' && minHour > activeTime) { - setMinTime(minutesList, minutesList.length) - } else if (type == 'hh' && minHour < activeTime) { - setMinTime(minutesList, 0) - } else if (type == 'mm' && minMinute == activeTime) { - setMinTime(secondsList, getAccordIndex(secondsList, 'second', minSecond)) - } else if (type == 'mm' && minMinute > activeTime) { - setMinTime(secondsList, secondsList.length) - } else if (type == 'mm' && minMinute < activeTime) { - setMinTime(secondsList, 0) - } - } else if (maxHour == activeHour.value) { - console.log(maxMinute, maxHour, activeTime) - if (type == 'hh' && maxHour == activeTime) { - console.log(1) - setMinTime(minutesList, 0) - setMinTime(secondsList, 0) - setMaxTime(minutesList, getAccordIndex(minutesList, 'minute', maxMinute)) - } else if (type == 'mm' && maxMinute == activeTime) { - setMaxTime(secondsList, getAccordIndex(secondsList, 'second', maxSecond)) - } else if (type == 'mm' && maxMinute > activeTime) { - console.log(4) - setMinTime(secondsList, 0) - setMaxTime(secondsList, minutesList.length) - } - } else { - if (activeHour.value < maxHour) { - setMinTime(minutesList, 0) - setMinTime(secondsList, 0) - } else { - setMaxTime(minutesList, minutesList.length) - setMaxTime(secondsList, minutesList.length) - } - } - } - // 设置最小时间 - const setMinTime = (arr: Array, length: number) => { - for (let i = 0; i < arr.length; i++) { - arr[i].isDisabled = false - } - if (length > 0) { - for (let i = 0; i < length; i++) { - arr[i].isDisabled = true - } - } - - } - - // 设置最大时间 - const setMaxTime = (arr: Array, length: number) => { - arr.map((item, index) => { - if (index > length) { - item.isDisabled = true - } - }) - } - - // selectedTimeChage 事件 const selectedTimeChage = () => { - ctx.emit('selectedTimeChage', timeVal.value) - } - - // slot -- 选择时间 - const chooseTime = (slotTime: TimeObj) => { - if (slotTime.type) { - if (slotTime.type.toLowerCase() == 'hh') { - activeHour.value = slotTime.time - } else if (slotTime.type.toLowerCase() == 'mm') { - activeMinute.value = slotTime.time - } else if (slotTime.type.toLowerCase() == 'ss') { - activeSecond.value = slotTime.time - } - timeVal.value = activeHour.value + ':' + activeMinute.value + ':' + activeSecond.value - } else { - const timeArr = slotTime.time.split(':') - activeHour.value = timeArr[0] - activeMinute.value = timeArr[1] - activeSecond.value = timeArr[2] - timeVal.value = slotTime.time - } - // initTimeFun(popupDom.value,timeVal.value) - } - - - // 区分浏览器内核,解决firefox中鼠标离开元素不继续滚动的问题 - const selectTimeFun = (e: MouseEvent) => { - e.stopPropagation() - const ua = navigator.userAgent - if (ua.indexOf('Firefox') > -1) { - initTimeFun(popupDom.value, timeVal.value) - } + isActive.value = false + showPopup.value = false + ctx.emit('selectedTimeChage', vModeValue.value) } onMounted(() => { - // console.log(ctx) - document.addEventListener('scroll', setPostionFun) - window.addEventListener('resize', setPostionFun) - document.addEventListener('click', cloasePopupFun) + getPopupPosition() + isOutOpen() + document.addEventListener('click', clickVerifyFun) + document.addEventListener('click', getTimeValue) + document.addEventListener('scroll', getPopupPosition) + window.addEventListener('resize', getPopupPosition) }) - onUnmounted(() => { - document.removeEventListener('scroll', setPostionFun) - window.removeEventListener('resize', setPostionFun) - document.removeEventListener('click', cloasePopupFun) + document.removeEventListener('click', clickVerifyFun) + document.removeEventListener('click', getTimeValue) + document.removeEventListener('scroll', getPopupPosition) + window.removeEventListener('resize', getPopupPosition) }) + watch(vModeValue, (newValue: string) => { + ctx.emit('update:modelValue', vModeValue.value) + if (newValue != props.minTime) { + showClearIcon.value = true + } else { + showClearIcon.value = false + } + }) - // 暴露子组件方法,用于slot时使用 ctx.expose({ - chooseTime + clearAll, chooseTime }) + return () => { return ( <> -
    +
    + + { + ctx.slots.customViewTemplate?.() + } + -
    - -
    @@ -84,7 +119,7 @@ export default defineComponent({ const chooseTime = ()=>{ let timeObj ={ time:'23', - type:'hh' + type:'mm' } slotDemo.value.chooseTime(timeObj) } @@ -127,31 +162,42 @@ export default defineComponent({ ``` ::: -### 事件 +### 获取数据方式 :::demo ```vue + ``` @@ -162,9 +208,9 @@ export default defineComponent({ |参数|类型|默认|说明|进度| |----|----|----|----|----| - |disabled| boolean |false|可选,禁用选择|基础版暂无样式| + |disabled| boolean |false|可选,禁用选择|基础版已完成| |timePickerWidth / time-picker-width |number|---|----|基础版已完成| - |format |string|'hh:mm:ss'|可选,传入格式化,控制时间格式|基础版暂未开始| + |format |string|'hh:mm:ss'|可选,传入格式化,控制时间格式|基础版已完成| |minTime / min-time|string|'00:00:00'|可选,限制最小可选时间|基础版已完成| |maxTime / max-time|string|'23:59:59'|可选,限制最大可选时间|基本版已完成| |customViewTemplate|TemplateRef|--|可选,自定义快捷设置时间或自定义操作区内容|基础版已完成| -- Gitee