init
This commit is contained in:
97
uni_modules/nutui-uni/components/datepicker/datepicker.ts
Normal file
97
uni_modules/nutui-uni/components/datepicker/datepicker.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { ExtractPropTypes, PropType } from 'vue'
|
||||
import { CANCEL_EVENT, CHANGE_EVENT, CONFIRM_EVENT, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import { makeNumberProp, makeNumericProp, makeStringProp, truthProp } from '../_utils'
|
||||
import type {
|
||||
DateLike,
|
||||
DatePickerBaseEvent,
|
||||
DatePickerChangeEvent,
|
||||
DatePickerFilter,
|
||||
DatePickerFormatter,
|
||||
DatePickerType,
|
||||
} from './type'
|
||||
|
||||
export const datepickerProps = {
|
||||
/**
|
||||
* @description 初始值
|
||||
*/
|
||||
modelValue: {
|
||||
type: [Number, String, Object] as PropType<DateLike>,
|
||||
},
|
||||
/**
|
||||
* @description 时间类型,可选值 `date`(年月日) `time`(时分秒) `year-month`(年月) `month-day`(月日) `datehour`(年月日时) `hour-minute`
|
||||
*/
|
||||
type: makeStringProp<DatePickerType>('date'),
|
||||
/**
|
||||
* @description 是否显示顶部导航
|
||||
*/
|
||||
showToolbar: truthProp,
|
||||
/**
|
||||
* @description 设置标题
|
||||
*/
|
||||
title: makeStringProp(''),
|
||||
/**
|
||||
* @description 确定按钮文案
|
||||
*/
|
||||
okText: makeStringProp(''),
|
||||
/**
|
||||
* @description 取消按钮文案
|
||||
*/
|
||||
cancelText: makeStringProp(''),
|
||||
/**
|
||||
* @description 每列是否展示中文
|
||||
*/
|
||||
isShowChinese: Boolean,
|
||||
/**
|
||||
* @description 分钟步进值
|
||||
*/
|
||||
minuteStep: makeNumberProp(1),
|
||||
/**
|
||||
* @description 开始日期
|
||||
*/
|
||||
minDate: {
|
||||
type: [Number, String, Object] as PropType<DateLike>,
|
||||
default: () => new Date(new Date().getFullYear() - 10, 0, 1),
|
||||
},
|
||||
/**
|
||||
* @description 结束日期
|
||||
*/
|
||||
maxDate: {
|
||||
type: [Number, String, Object] as PropType<DateLike>,
|
||||
default: () => new Date(new Date().getFullYear() + 10, 11, 31),
|
||||
},
|
||||
/**
|
||||
* @description 选项格式化函数
|
||||
*/
|
||||
formatter: Function as PropType<DatePickerFormatter>,
|
||||
/**
|
||||
* @description 选项过滤函数
|
||||
*/
|
||||
filter: Function as PropType<DatePickerFilter>,
|
||||
/**
|
||||
* @description 是否开启3D效果
|
||||
*/
|
||||
threeDimensional: Boolean,
|
||||
/**
|
||||
* @description 惯性滚动时长
|
||||
*/
|
||||
swipeDuration: makeNumericProp(1000),
|
||||
/**
|
||||
* @description 可见的选项个数
|
||||
*/
|
||||
visibleOptionNum: makeNumericProp(7),
|
||||
/**
|
||||
* @description 选项高度
|
||||
*/
|
||||
optionHeight: makeNumericProp(36),
|
||||
}
|
||||
|
||||
export type DatePickerProps = ExtractPropTypes<typeof datepickerProps>
|
||||
|
||||
export const datepickerEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (val: Date) => val instanceof Object,
|
||||
[CHANGE_EVENT]: (evt: DatePickerChangeEvent) => evt instanceof Object,
|
||||
[CONFIRM_EVENT]: (evt: DatePickerBaseEvent) => evt instanceof Object,
|
||||
[CANCEL_EVENT]: (evt: DatePickerBaseEvent) => evt instanceof Object,
|
||||
}
|
||||
|
||||
export type DatePickerEmits = typeof datepickerEmits
|
||||
405
uni_modules/nutui-uni/components/datepicker/datepicker.vue
Normal file
405
uni_modules/nutui-uni/components/datepicker/datepicker.vue
Normal file
@@ -0,0 +1,405 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, nextTick, onBeforeMount, reactive, watch } from 'vue'
|
||||
import { CANCEL_EVENT, CONFIRM_EVENT, PREFIX, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import { isDate, isEqualValue, padZero } from '../_utils'
|
||||
import type { PickerBaseEvent, PickerChangeEvent } from '../picker'
|
||||
import NutPicker from '../picker/picker.vue'
|
||||
import type { PickerOption } from '../pickercolumn'
|
||||
import { datepickerEmits, datepickerProps } from './datepicker'
|
||||
import type { DateLike, DatePickerBaseEvent, DatePickerColumnType, DatePickerRangeItem } from './type'
|
||||
|
||||
const props = defineProps(datepickerProps)
|
||||
|
||||
const emit = defineEmits(datepickerEmits)
|
||||
|
||||
const ZH_CN_LOCALES: {
|
||||
[props: string]: string
|
||||
} = {
|
||||
day: '日',
|
||||
year: '年',
|
||||
month: '月',
|
||||
hour: '时',
|
||||
minute: '分',
|
||||
seconds: '秒',
|
||||
}
|
||||
|
||||
interface State {
|
||||
currentDate: Date
|
||||
selectedValue: string[]
|
||||
}
|
||||
|
||||
const state: State = reactive({
|
||||
currentDate: new Date(),
|
||||
selectedValue: [],
|
||||
})
|
||||
|
||||
function normalizeDate(value?: DateLike) {
|
||||
if (value == null)
|
||||
return new Date()
|
||||
|
||||
if (isDate(value))
|
||||
return value
|
||||
|
||||
return new Date(value)
|
||||
}
|
||||
|
||||
const innerMinDate = computed(() => {
|
||||
return normalizeDate(props.minDate)
|
||||
})
|
||||
|
||||
const innerMaxDate = computed(() => {
|
||||
return normalizeDate(props.maxDate)
|
||||
})
|
||||
|
||||
function formatValue(value: Date) {
|
||||
return new Date(Math.min(Math.max(value.getTime(), innerMinDate.value.getTime()), innerMaxDate.value.getTime()))
|
||||
}
|
||||
|
||||
function getMonthEndDay(year: number, month: number): number {
|
||||
return 32 - new Date(year, month - 1, 32).getDate()
|
||||
}
|
||||
|
||||
function getBoundary(type: 'min' | 'max', value: Date) {
|
||||
const boundary = type === 'min' ? innerMinDate.value : innerMaxDate.value
|
||||
|
||||
const year = boundary.getFullYear()
|
||||
let month = 1
|
||||
let date = 1
|
||||
let hour = 0
|
||||
let minute = 0
|
||||
|
||||
if (type === 'max') {
|
||||
month = 12
|
||||
date = getMonthEndDay(value.getFullYear(), value.getMonth() + 1)
|
||||
hour = 23
|
||||
minute = 59
|
||||
}
|
||||
|
||||
let seconds = minute
|
||||
if (value.getFullYear() === year) {
|
||||
month = boundary.getMonth() + 1
|
||||
if (value.getMonth() + 1 === month) {
|
||||
date = boundary.getDate()
|
||||
if (value.getDate() === date) {
|
||||
hour = boundary.getHours()
|
||||
if (value.getHours() === hour) {
|
||||
minute = boundary.getMinutes()
|
||||
if (value.getMinutes() === minute)
|
||||
seconds = boundary.getSeconds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[`${type}Year`]: year,
|
||||
[`${type}Month`]: month,
|
||||
[`${type}Date`]: date,
|
||||
[`${type}Hour`]: hour,
|
||||
[`${type}Minute`]: minute,
|
||||
[`${type}Seconds`]: seconds,
|
||||
}
|
||||
}
|
||||
|
||||
const ranges = computed<DatePickerRangeItem[]>(() => {
|
||||
const { minYear, minDate, minMonth, minHour, minMinute, minSeconds } = getBoundary('min', state.currentDate)
|
||||
|
||||
const { maxYear, maxDate, maxMonth, maxHour, maxMinute, maxSeconds } = getBoundary('max', state.currentDate)
|
||||
|
||||
return generateList([
|
||||
{
|
||||
type: 'year',
|
||||
range: [minYear, maxYear],
|
||||
},
|
||||
{
|
||||
type: 'month',
|
||||
range: [minMonth, maxMonth],
|
||||
},
|
||||
{
|
||||
type: 'day',
|
||||
range: [minDate, maxDate],
|
||||
},
|
||||
{
|
||||
type: 'hour',
|
||||
range: [minHour, maxHour],
|
||||
},
|
||||
{
|
||||
type: 'minute',
|
||||
range: [minMinute, maxMinute],
|
||||
},
|
||||
{
|
||||
type: 'seconds',
|
||||
range: [minSeconds, maxSeconds],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
const columns = computed(() => {
|
||||
return ranges.value.map((item, columnIndex) => {
|
||||
return generateValue(item.range[0], item.range[1], getDateIndex(item.type), item.type, columnIndex)
|
||||
})
|
||||
})
|
||||
|
||||
function handleChange({
|
||||
columnIndex,
|
||||
selectedValue,
|
||||
selectedOptions,
|
||||
}: PickerChangeEvent) {
|
||||
const formatDate: (number | string)[] = [...selectedValue]
|
||||
|
||||
if (props.type === 'month-day' && formatDate.length < 3)
|
||||
formatDate.unshift(new Date(state.currentDate || innerMinDate.value || innerMaxDate.value).getFullYear())
|
||||
|
||||
if (props.type === 'year-month' && formatDate.length < 3)
|
||||
formatDate.push(new Date(state.currentDate || innerMinDate.value || innerMaxDate.value).getDate())
|
||||
|
||||
const year = Number(formatDate[0])
|
||||
const month = Number(formatDate[1]) - 1
|
||||
const day = Math.min(Number(formatDate[2]), getMonthEndDay(Number(formatDate[0]), Number(formatDate[1])))
|
||||
|
||||
let date: Date | null = null
|
||||
if (props.type === 'date' || props.type === 'month-day' || props.type === 'year-month') {
|
||||
date = new Date(year, month, day)
|
||||
}
|
||||
else if (props.type === 'datetime') {
|
||||
date = new Date(year, month, day, Number(formatDate[3]), Number(formatDate[4]))
|
||||
}
|
||||
else if (props.type === 'datehour') {
|
||||
date = new Date(year, month, day, Number(formatDate[3]))
|
||||
}
|
||||
else if (props.type === 'hour-minute' || props.type === 'time') {
|
||||
date = new Date(state.currentDate)
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth()
|
||||
const day = date.getDate()
|
||||
date = new Date(year, month, day, Number(formatDate[0]), Number(formatDate[1]), Number(formatDate[2] || 0))
|
||||
}
|
||||
|
||||
state.currentDate = formatValue(date!)
|
||||
|
||||
emit('change', { date: date!, columnIndex, selectedValue, selectedOptions })
|
||||
}
|
||||
|
||||
function formatterOption(type: DatePickerColumnType, value: string | number) {
|
||||
const { formatter, isShowChinese } = props
|
||||
|
||||
const text = padZero(value, 2)
|
||||
|
||||
let option: PickerOption
|
||||
if (formatter)
|
||||
option = formatter(type, { text, value: text })
|
||||
else
|
||||
option = { text: `${text}${isShowChinese ? ZH_CN_LOCALES[type] : ''}`, value: text }
|
||||
|
||||
return option
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
* @param value 当前显示的值
|
||||
* @param type 类型
|
||||
* @param columnIndex
|
||||
*/
|
||||
function generateValue(min: number, max: number, value: number | string, type: DatePickerColumnType, columnIndex: number) {
|
||||
const options: PickerOption[] = []
|
||||
|
||||
let index = 0
|
||||
while (min <= max) {
|
||||
options.push(formatterOption(type, min))
|
||||
|
||||
if (type === 'minute')
|
||||
min += props.minuteStep
|
||||
else
|
||||
min += 1
|
||||
|
||||
if (min <= Number(value))
|
||||
index += 1
|
||||
}
|
||||
|
||||
state.selectedValue[columnIndex] = options[index]?.value as string
|
||||
|
||||
return props.filter ? props.filter(type, options) : options
|
||||
}
|
||||
|
||||
function getDateIndex(type: DatePickerColumnType) {
|
||||
if (type === 'year')
|
||||
return state.currentDate.getFullYear()
|
||||
|
||||
if (type === 'month')
|
||||
return state.currentDate.getMonth() + 1
|
||||
|
||||
if (type === 'day')
|
||||
return state.currentDate.getDate()
|
||||
|
||||
if (type === 'hour')
|
||||
return state.currentDate.getHours()
|
||||
|
||||
if (type === 'minute')
|
||||
return state.currentDate.getMinutes()
|
||||
|
||||
if (type === 'seconds')
|
||||
return state.currentDate.getSeconds()
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function convertEvent({ selectedValue, selectedOptions }: PickerBaseEvent): DatePickerBaseEvent {
|
||||
let date: Date | null = null
|
||||
|
||||
switch (props.type) {
|
||||
case 'date':
|
||||
case 'datehour':
|
||||
case 'datetime':
|
||||
case 'year-month': {
|
||||
const [
|
||||
year = 0,
|
||||
month = 0,
|
||||
day = 0,
|
||||
hour = 0,
|
||||
minute = 0,
|
||||
seconds = 0,
|
||||
] = selectedValue
|
||||
date = new Date(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(seconds))
|
||||
break
|
||||
}
|
||||
case 'time':
|
||||
case 'hour-minute': {
|
||||
const [
|
||||
hour = 0,
|
||||
minute = 0,
|
||||
seconds = 0,
|
||||
] = selectedValue
|
||||
date = new Date(0, 0, 0, Number(hour), Number(minute), Number(seconds))
|
||||
break
|
||||
}
|
||||
case 'month-day': {
|
||||
const [
|
||||
month = 0,
|
||||
day = 0,
|
||||
] = selectedValue
|
||||
date = new Date(0, Number(month) - 1, Number(day))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (date == null)
|
||||
date = new Date()
|
||||
|
||||
return {
|
||||
date,
|
||||
selectedValue,
|
||||
selectedOptions,
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel(event: PickerBaseEvent) {
|
||||
emit(CANCEL_EVENT, convertEvent(event))
|
||||
}
|
||||
|
||||
function handleConfirm(event: PickerBaseEvent) {
|
||||
emit(CONFIRM_EVENT, convertEvent(event))
|
||||
}
|
||||
|
||||
function generateList<T>(list: T[]) {
|
||||
switch (props.type) {
|
||||
case 'date':
|
||||
return list.slice(0, 3)
|
||||
case 'datetime':
|
||||
return list.slice(0, 5)
|
||||
case 'time':
|
||||
return list.slice(3, 6)
|
||||
case 'year-month':
|
||||
return list.slice(0, 2)
|
||||
case 'month-day':
|
||||
return list.slice(1, 3)
|
||||
case 'datehour':
|
||||
return list.slice(0, 4)
|
||||
case 'hour-minute':
|
||||
return list.slice(3, 5)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
function getSelectedValue(time: Date) {
|
||||
return generateList([
|
||||
time.getFullYear(),
|
||||
time.getMonth() + 1,
|
||||
time.getDate(),
|
||||
time.getHours(),
|
||||
time.getMinutes(),
|
||||
time.getSeconds(),
|
||||
].map(it => String(it)))
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
state.currentDate = formatValue(normalizeDate(props.modelValue))
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
const newValue = formatValue(normalizeDate(value))
|
||||
|
||||
if (!isEqualValue(newValue, state.currentDate)) {
|
||||
state.currentDate = newValue
|
||||
state.selectedValue = getSelectedValue(newValue)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => state.currentDate,
|
||||
(value) => {
|
||||
if (!isEqualValue(value, normalizeDate(props.modelValue))) {
|
||||
emit(UPDATE_MODEL_EVENT, value)
|
||||
|
||||
nextTick(() => {
|
||||
state.selectedValue = getSelectedValue(value)
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-date-picker`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NutPicker
|
||||
v-model="state.selectedValue"
|
||||
:show-toolbar="props.showToolbar"
|
||||
:title="props.title"
|
||||
:ok-text="props.okText"
|
||||
:cancel-text="props.cancelText"
|
||||
:columns="columns"
|
||||
:three-dimensional="props.threeDimensional"
|
||||
:swipe-duration="props.swipeDuration"
|
||||
:visible-option-num="props.visibleOptionNum"
|
||||
:option-height="props.optionHeight"
|
||||
@change="handleChange"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<template #top>
|
||||
<slot name="top" />
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
</NutPicker>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
</style>
|
||||
1
uni_modules/nutui-uni/components/datepicker/index.scss
Normal file
1
uni_modules/nutui-uni/components/datepicker/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import "../picker/index";
|
||||
2
uni_modules/nutui-uni/components/datepicker/index.ts
Normal file
2
uni_modules/nutui-uni/components/datepicker/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './datepicker'
|
||||
export * from './type'
|
||||
27
uni_modules/nutui-uni/components/datepicker/type.ts
Normal file
27
uni_modules/nutui-uni/components/datepicker/type.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { PickerBaseEvent, PickerChangeEvent } from '../picker'
|
||||
import type { PickerOption } from '../pickercolumn'
|
||||
|
||||
export type DateLike = number | string | Date
|
||||
|
||||
export const datepickerType = ['date', 'time', 'year-month', 'month-day', 'datehour', 'hour-minute', 'datetime'] as const
|
||||
export type DatePickerType = (typeof datepickerType)[number]
|
||||
|
||||
export const datepickerColumnType = ['year', 'month', 'day', 'hour', 'minute', 'seconds'] as const
|
||||
export type DatePickerColumnType = (typeof datepickerColumnType)[number]
|
||||
|
||||
export interface DatePickerBaseEvent extends PickerBaseEvent {
|
||||
date: Date
|
||||
}
|
||||
|
||||
export interface DatePickerChangeEvent extends DatePickerBaseEvent, PickerChangeEvent {
|
||||
|
||||
}
|
||||
|
||||
export type DatePickerFormatter = (type: DatePickerColumnType, option: PickerOption) => PickerOption
|
||||
|
||||
export type DatePickerFilter = (type: DatePickerColumnType, options: PickerOption[]) => PickerOption[]
|
||||
|
||||
export interface DatePickerRangeItem {
|
||||
type: DatePickerColumnType
|
||||
range: [number, number]
|
||||
}
|
||||
Reference in New Issue
Block a user