更新:登录功能

This commit is contained in:
2025-11-04 12:37:04 +08:00
commit a21fb92916
897 changed files with 51500 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
@import "./../common/abstracts/_mixin.scss";
@import "./../common/abstracts/variable.scss";
.wot-theme-dark {
@include b(datetime-picker) {
@include e(placeholder) {
color: $-dark-color-gray;
}
:deep(.wd-datetime-picker__arrow),
:deep(.wd-datetime-picker__clear) {
color: $-dark-color;
}
@include e(action) {
@include m(cancel) {
color: $-dark-color;
}
}
@include e(region) {
color: $-dark-color;
@include when(active) {
background: $-picker-region-bg-active-color;
color: $-dark-color;
}
}
}
}
@include b(datetime-picker) {
@include edeep(cell) {
@include when(disabled) {
.wd-cell__value {
color: $-input-disabled-color;
cursor: not-allowed;
}
}
@include when(error) {
.wd-cell__value {
color: $-input-error-color;
}
.wd-datetime-picker__arrow {
color: $-input-error-color;
}
}
@include when(large) {
.wd-datetime-picker__arrow {
font-size: $-cell-icon-size-large;
}
}
.wd-cell__value--ellipsis {
view {
@include lineEllipsis;
width: 100%;
}
text {
@include lineEllipsis;
max-width: 100%;
}
}
}
@include e(placeholder) {
color: $-input-placeholder-color;
}
@include edeep(arrow) {
display: block;
font-size: $-cell-icon-size;
color: $-cell-arrow-color;
line-height: $-cell-line-height;
}
@include edeep(clear) {
display: block;
font-size: $-cell-icon-size;
color: $-cell-clear-color;
line-height: $-cell-line-height;
}
@include edeep(popup) {
border-radius: 16px 16px 0px 0px;
}
@include e(wraper) {
padding-bottom: var(--window-bottom);
}
@include e(toolbar) {
position: relative;
display: flex;
font-size: $-picker-toolbar-fs;
height: $-picker-toolbar-height;
line-height: $-picker-action-height;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
}
@include e(action) {
display: block;
border: none;
outline: none;
font-size: $-picker-toolbar-fs;
color: $-picker-toolbar-finish-color;
background: transparent;
padding: 24px 15px 14px 15px;
@include m(cancel) {
color: $-picker-toolbar-cancel-color;
}
@include when(loading) {
color: $-picker-loading-button-color;
}
}
@include e(title) {
display: block;
float: 1;
color: $-picker-toolbar-title-color;
}
@include e(region-tabs) {
display: flex;
}
@include e(region) {
width: 50%;
display: inline-block;
color: $-picker-region-color;
text-align: center;
padding: 14px 0;
font-size: $-picker-region-fs;
line-height: 16px;
transition: all 0.15s ease-out;
@include when(active) {
background: $-picker-region-bg-active-color;
color: $-color-white;
}
}
@include e(region-time) {
font-size: 16px;
margin-top: 2px;
}
@include e(hidden) {
visibility: hidden;
overflow: hidden;
height: 0;
}
@include e(show) {
visibility: visible;
height: auto;
}
}

View File

@@ -0,0 +1,231 @@
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
import type { DateTimeType, DatetimePickerViewFilter, DatetimePickerViewFormatter } from '../wd-datetime-picker-view/types'
import type { FormItemRule } from '../wd-form/types'
export const datetimePickerProps = {
...baseProps,
/**
* 选择器左侧文案label可以不传
*/
label: String,
/**
* 选择器占位符
*/
placeholder: String,
/**
* 禁用
*/
disabled: makeBooleanProp(false),
/**
* 只读
*/
readonly: makeBooleanProp(false),
/**
* 加载中
*/
loading: makeBooleanProp(false),
/**
* 加载的颜色,只能使用十六进制的色值写法,且不能使用缩写
*/
loadingColor: makeStringProp('#4D80F0'),
/**
* 弹出层标题
*/
title: String,
/**
* 取消按钮文案
*/
cancelButtonText: String,
/**
* 确认按钮文案
*/
confirmButtonText: String,
/**
* 是否必填
*/
required: makeBooleanProp(false),
/**
* 设置选择器大小可选值large
*/
size: String,
/**
* 设置左侧标题宽度
*/
labelWidth: makeStringProp('33%'),
/**
* 是否为错误状态,错误状态时右侧内容为红色
*/
error: makeBooleanProp(false),
/**
* 选择器的值靠右展示
*/
alignRight: makeBooleanProp(false),
/**
* 点击遮罩是否关闭
*/
closeOnClickModal: makeBooleanProp(true),
/**
* 弹出面板是否设置底部安全距离iphone X 类型的机型)
*/
safeAreaInsetBottom: makeBooleanProp(true),
/**
* 是否超出隐藏
*/
ellipsis: makeBooleanProp(false),
/**
* picker内部滚筒高
*/
columnsHeight: makeNumberProp(217),
/**
* 选项的key
*/
valueKey: makeStringProp('value'),
/**
* 选项的label
*/
labelKey: makeStringProp('label'),
/**
* 选中项,当 type 为 time 时,类型为字符串;当 type 为 Array 时,类型为范围选择;否则为 时间戳
*/
modelValue: makeRequiredProp([String, Number, Array] as PropType<string | number | Array<string | number>>),
/**
* 选择器类型可选值为date / year-month / time
*/
type: makeStringProp<DateTimeType>('datetime'),
/**
* 最小日期
*/
minDate: makeNumberProp(new Date(new Date().getFullYear() - 10, 0, 1).getTime()),
/**
* 最大日期
*/
maxDate: makeNumberProp(new Date(new Date().getFullYear() + 10, 11, 31, 23, 59, 59).getTime()),
/**
* 最小小时time类型时生效
*/
minHour: makeNumberProp(0),
/**
* 最大小时time类型时生效
*/
maxHour: makeNumberProp(23),
/**
* 最小分钟time类型时生效
*/
minMinute: makeNumberProp(0),
/**
* 最大分钟time类型时生效
*/
maxMinute: makeNumberProp(59),
/**
* 是否启用秒选择,仅在 time 和 datetime 类型下生效
*/
useSecond: makeBooleanProp(false),
/**
* 最小秒数,仅在 time 和 datetime 类型下生效
*/
minSecond: makeNumberProp(0),
/**
* 最大秒数,仅在 time 和 datetime 类型下生效
*/
maxSecond: makeNumberProp(59),
/**
* 自定义过滤选项的函数,返回列的选项数组
*/
filter: Function as PropType<DatetimePickerViewFilter>,
/**
* 自定义弹出层选项文案的格式化函数,返回一个字符串
*/
formatter: Function as PropType<DatetimePickerViewFormatter>,
/**
* 自定义展示文案的格式化函数,返回一个字符串
*/
displayFormat: Function as PropType<DatetimePickerDisplayFormat>,
/**
* 确定前校验函数,接收 (value, resolve, picker) 参数,通过 resolve 继续执行 pickerresolve 接收1个boolean参数
*/
beforeConfirm: Function as PropType<DatetimePickerBeforeConfirm>,
/**
* 在区域选择模式下自定义展示tab标签文案的格式化函数返回一个字符串
*/
displayFormatTabLabel: Function as PropType<DatetimePickerDisplayFormatTabLabel>,
/**
* 默认日期,类型保持与 value 一致,打开面板时面板自动选到默认日期
*/
defaultValue: [String, Number, Array] as PropType<string | number | Array<string | number>>,
/**
* 弹窗层级
*/
zIndex: makeNumberProp(15),
/**
* 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的
*/
prop: String,
/**
* 表单验证规则结合wd-form组件使用
*/
rules: makeArrayProp<FormItemRule>(),
/**
* picker cell 外部自定义样式
*/
customCellClass: makeStringProp(''),
/**
* pickerView 外部自定义样式
*/
customViewClass: makeStringProp(''),
/**
* label 外部自定义样式
*/
customLabelClass: makeStringProp(''),
/**
* value 外部自定义样式
*/
customValueClass: makeStringProp(''),
/**
* 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
*/
immediateChange: makeBooleanProp(false),
/**
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
*/
rootPortal: makeBooleanProp(false),
/**
* 显示清空按钮
*/
clearable: makeBooleanProp(false),
/**
* 必填标记位置可选值before、after
*/
markerSide: makeStringProp<'before' | 'after'>('before')
}
export type DatetimePickerDisplayFormat = (items: Record<string, any>[]) => string
export type DatetimePickerBeforeConfirm = (
value: number | string | (number | string)[],
resolve: (isPass: boolean) => void,
picker: DatetimePickerInstance
) => void
export type DatetimePickerDisplayFormatTabLabel = (items: Record<string, any>[]) => string
export type DatetimePickerExpose = {
/**
* 打开picker弹框
*/
open: () => void
/**
* 关闭picker弹框
*/
close: () => void
/**
* 设置加载状态
* @param loading 加载状态
* @returns
*/
setLoading: (loading: boolean) => void
}
export type DatetimePickerProps = ExtractPropTypes<typeof datetimePickerProps>
export type DatetimePickerInstance = ComponentPublicInstance<DatetimePickerProps, DatetimePickerExpose>

View File

@@ -0,0 +1,801 @@
<template>
<view :class="`wd-datetime-picker ${customClass}`" :style="customStyle">
<wd-cell
v-if="!$slots.default"
:title="label"
:required="required"
:size="size"
:title-width="labelWidth"
:prop="prop"
:rules="rules"
:clickable="!disabled && !readonly"
:value-align="alignRight ? 'right' : 'left'"
:custom-class="cellClass"
:custom-style="customStyle"
:custom-title-class="customLabelClass"
:custom-value-class="customValueClass"
:ellipsis="ellipsis"
:use-title-slot="!!$slots.label"
:marker-side="markerSide"
@click="showPopup"
>
<template v-if="$slots.label" #title>
<slot name="label"></slot>
</template>
<template #default>
<template v-if="region">
<view v-if="isArray(showValue)">
<text :class="showValue[0] ? '' : 'wd-datetime-picker__placeholder'">
{{ showValue[0] ? showValue[0] : placeholder || translate('placeholder') }}
</text>
{{ translate('to') }}
<text :class="showValue[1] ? '' : 'wd-datetime-picker__placeholder'">
{{ showValue[1] ? showValue[1] : placeholder || translate('placeholder') }}
</text>
</view>
<view v-else class="wd-datetime-picker__cell-placeholder">
{{ placeholder || translate('placeholder') }}
</view>
</template>
<view v-else :class="showValue ? '' : 'wd-datetime-picker__placeholder'">
{{ showValue ? showValue : placeholder || translate('placeholder') }}
</view>
</template>
<template #right-icon>
<wd-icon v-if="showArrow" custom-class="wd-datetime-picker__arrow" name="arrow-right" />
<view v-else-if="showClear" @click.stop="handleClear">
<wd-icon custom-class="wd-datetime-picker__clear" name="error-fill" />
</view>
</template>
</wd-cell>
<view v-else @click="showPopup">
<slot></slot>
</view>
<!--弹出层picker-view 在隐藏时修改值会触发多次change事件从而导致所有列选中第一项因此picker在关闭时不隐藏 -->
<wd-popup
v-model="popupShow"
position="bottom"
:hide-when-close="false"
:close-on-click-modal="closeOnClickModal"
:safe-area-inset-bottom="safeAreaInsetBottom"
:z-index="zIndex"
:root-portal="rootPortal"
@close="onCancel"
custom-class="wd-datetime-picker__popup"
>
<view class="wd-datetime-picker__wraper">
<!--toolBar-->
<view class="wd-datetime-picker__toolbar" @touchmove="noop">
<!--取消按钮-->
<view class="wd-datetime-picker__action wd-datetime-picker__action--cancel" @click="onCancel">
{{ cancelButtonText || translate('cancel') }}
</view>
<!--标题-->
<view v-if="title" class="wd-datetime-picker__title">{{ title }}</view>
<!--确定按钮-->
<view :class="`wd-datetime-picker__action ${loading || isLoading ? 'is-loading' : ''}`" @click="onConfirm">
{{ confirmButtonText || translate('confirm') }}
</view>
</view>
<!-- 区域选择tab展示 -->
<view v-if="region" class="wd-datetime-picker__region-tabs">
<view :class="`wd-datetime-picker__region ${showStart ? 'is-active' : ''} `" @click="tabChange">
<view>{{ translate('start') }}</view>
<view class="wd-datetime-picker__region-time">{{ showTabLabel[0] }}</view>
</view>
<view :class="`wd-datetime-picker__region ${showStart ? '' : 'is-active'}`" @click="tabChange">
<view>{{ translate('end') }}</view>
<view class="wd-datetime-picker__region-time">{{ showTabLabel[1] }}</view>
</view>
</view>
<!--datetimePickerView-->
<view :class="showStart ? 'wd-datetime-picker__show' : 'wd-datetime-picker__hidden'">
<wd-datetime-picker-view
:custom-class="customViewClass"
ref="datetimePickerView"
:type="type"
v-model="innerValue"
:loading="loading || isLoading"
:loading-color="loadingColor"
:columns-height="columnsHeight"
:value-key="valueKey"
:label-key="labelKey"
:formatter="formatter"
:filter="filter"
:column-formatter="isArray(modelValue) ? startColumnFormatter : undefined"
:max-hour="maxHour"
:min-hour="minHour"
:max-date="maxDate"
:min-date="minDate"
:max-minute="maxMinute"
:min-minute="minMinute"
:use-second="useSecond"
:min-second="minSecond"
:max-second="maxSecond"
:immediate-change="immediateChange"
@change="onChangeStart"
@pickstart="onPickStart"
@pickend="onPickEnd"
/>
</view>
<view :class="showStart ? 'wd-datetime-picker__hidden' : 'wd-datetime-picker__show'">
<wd-datetime-picker-view
:custom-class="customViewClass"
ref="datetimePickerView1"
:type="type"
v-model="endInnerValue"
:loading="loading || isLoading"
:loading-color="loadingColor"
:columns-height="columnsHeight"
:value-key="valueKey"
:label-key="labelKey"
:formatter="formatter"
:filter="filter"
:column-formatter="isArray(modelValue) ? endColumnFormatter : undefined"
:max-hour="maxHour"
:min-hour="minHour"
:max-date="maxDate"
:min-date="minDate"
:max-minute="maxMinute"
:min-minute="minMinute"
:use-second="useSecond"
:min-second="minSecond"
:max-second="maxSecond"
:immediate-change="immediateChange"
@change="onChangeEnd"
@pickstart="onPickStart"
@pickend="onPickEnd"
/>
</view>
</view>
</wd-popup>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-datetime-picker',
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import wdPopup from '../wd-popup/wd-popup.vue'
import wdDatetimePickerView from '../wd-datetime-picker-view/wd-datetime-picker-view.vue'
import wdCell from '../wd-cell/wd-cell.vue'
import wdIcon from '../wd-icon/wd-icon.vue'
import { computed, getCurrentInstance, nextTick, onBeforeMount, onMounted, ref, watch } from 'vue'
import { deepClone, isArray, isDef, isEqual, isFunction, padZero } from '../common/util'
import { type DatetimePickerViewInstance, type DatetimePickerViewColumnType, type DatetimePickerViewExpose } from '../wd-datetime-picker-view/types'
import { useTranslate } from '../composables/useTranslate'
import { datetimePickerProps, type DatetimePickerExpose } from './types'
import dayjs from '../../dayjs'
import { getPickerValue } from '../wd-datetime-picker-view/util'
const props = defineProps(datetimePickerProps)
const emit = defineEmits(['change', 'open', 'toggle', 'cancel', 'confirm', 'clear', 'update:modelValue'])
const { translate } = useTranslate('datetime-picker')
const datetimePickerView = ref<DatetimePickerViewInstance>()
const datetimePickerView1 = ref<DatetimePickerViewInstance>()
const showValue = ref<string | Date | Array<string | Date>>('')
const popupShow = ref<boolean>(false)
const showStart = ref<boolean>(true)
const region = ref<boolean>(false)
const showTabLabel = ref<string[]>([])
const innerValue = ref<string | number>('')
const endInnerValue = ref<string | number>('')
const isPicking = ref<boolean>(false) // 判断pickview是否还在滑动中
const hasConfirmed = ref<boolean>(false) // 判断用户是否点击了确认按钮
const isLoading = ref<boolean>(false) // 加载
const { proxy } = getCurrentInstance() as any
const cellClass = computed(() => {
const classes = ['wd-datetime-picker__cell']
if (props.disabled) classes.push('is-disabled')
if (props.readonly) classes.push('is-readonly')
if (props.error) classes.push('is-error')
return classes.join(' ')
})
watch(
() => props.modelValue,
(val, oldVal) => {
if (isEqual(val, oldVal)) return
if (isArray(val)) {
region.value = true
innerValue.value = deepClone(getDefaultInnerValue(true))
endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
} else {
// 每次value更新时都需要刷新整个列表
innerValue.value = deepClone(getDefaultInnerValue())
}
nextTick(() => {
setShowValue(false, false, true)
})
},
{
deep: true,
immediate: true
}
)
watch(
() => props.displayFormat,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of displayFormat must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.filter,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of filter must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.formatter,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of formatter must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.beforeConfirm,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of beforeConfirm must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.displayFormatTabLabel,
(fn) => {
if (fn && !isFunction(fn)) {
console.error('The type of displayFormatTabLabel must be Function')
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.defaultValue,
(val) => {
if (isArray(val) || region.value) {
innerValue.value = deepClone(getDefaultInnerValue(true))
endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
} else {
innerValue.value = deepClone(getDefaultInnerValue())
}
},
{
deep: true,
immediate: true
}
)
// 是否展示清除按钮
const showClear = computed(() => {
return (
props.clearable &&
!props.disabled &&
!props.readonly &&
((!isArray(showValue.value) && showValue.value) || (isArray(showValue.value) && (showValue.value[0] || showValue.value[1])))
)
})
// 是否展示箭头
const showArrow = computed(() => {
return !props.disabled && !props.readonly && !showClear.value
})
/**
* @description 处理时间边界值判断
* @param isStart 是否是开始时间
* @param columnType 当前列类型
* @param value 当前值
* @param currentArray 当前完整选择的数组
* @param boundary 边界值数组
* @returns 是否超出边界
*/
function handleBoundaryValue(
isStart: boolean,
columnType: DatetimePickerViewColumnType,
value: number,
currentArray: number[],
boundary: number[]
): boolean {
const { type, useSecond } = props
switch (type) {
case 'datetime': {
const [year, month, date, hour, minute, second] = boundary
if (columnType === 'year') {
return isStart ? value > year : value < year
}
if (columnType === 'month' && currentArray[0] === year) {
return isStart ? value > month : value < month
}
if (columnType === 'date' && currentArray[0] === year && currentArray[1] === month) {
return isStart ? value > date : value < date
}
if (columnType === 'hour' && currentArray[0] === year && currentArray[1] === month && currentArray[2] === date) {
return isStart ? value > hour : value < hour
}
if (columnType === 'minute' && currentArray[0] === year && currentArray[1] === month && currentArray[2] === date && currentArray[3] === hour) {
return isStart ? value > minute : value < minute
}
if (
useSecond &&
columnType === 'second' &&
currentArray[0] === year &&
currentArray[1] === month &&
currentArray[2] === date &&
currentArray[3] === hour &&
currentArray[4] === minute
) {
return isStart ? value > second : value < second
}
break
}
case 'year-month': {
const [year, month] = boundary
if (columnType === 'year') {
return isStart ? value > year : value < year
}
if (columnType === 'month' && currentArray[0] === year) {
return isStart ? value > month : value < month
}
break
}
case 'year': {
const [year] = boundary
if (columnType === 'year') {
return isStart ? value > year : value < year
}
break
}
case 'date': {
const [year, month, date] = boundary
if (columnType === 'year') {
return isStart ? value > year : value < year
}
if (columnType === 'month' && currentArray[0] === year) {
return isStart ? value > month : value < month
}
if (columnType === 'date' && currentArray[0] === year && currentArray[1] === month) {
return isStart ? value > date : value < date
}
break
}
case 'time': {
const [hour, minute, second] = boundary
if (columnType === 'hour') {
return isStart ? value > hour : value < hour
}
if (columnType === 'minute' && currentArray[0] === hour) {
return isStart ? value > minute : value < minute
}
if (useSecond && columnType === 'second' && currentArray[0] === hour && currentArray[1] === minute) {
return isStart ? value > second : value < second
}
break
}
}
return false
}
function startColumnFormatter(picker: DatetimePickerViewExpose) {
return customColumnFormatter(picker, 'start')
}
function endColumnFormatter(picker: DatetimePickerViewExpose) {
return customColumnFormatter(picker, 'end')
}
/**
* @description 自定义列项筛选规则
*/
const customColumnFormatter = (picker: DatetimePickerViewExpose, pickerType: 'start' | 'end') => {
if (!picker) return []
const { type } = props
const startSymbol = pickerType === 'start'
const { formatter } = props
const start = picker.correctValue(innerValue.value)
const end = picker.correctValue(endInnerValue.value)
const currentValue = startSymbol ? getPickerValue(start, type, props.useSecond) : getPickerValue(end, type, props.useSecond)
const boundary = startSymbol ? getPickerValue(end, type, props.useSecond) : getPickerValue(start, type, props.useSecond)
const columns = picker.getOriginColumns()
return columns.map((column, _) => {
return column.values.map((value) => {
const disabled = handleBoundaryValue(startSymbol, column.type, value, currentValue, boundary)
return {
label: formatter ? formatter(column.type, padZero(value)) : padZero(value),
value,
disabled
}
})
})
}
onBeforeMount(() => {
const { modelValue: value } = props
if (isArray(value)) {
region.value = true
innerValue.value = deepClone(getDefaultInnerValue(true))
endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
} else {
innerValue.value = deepClone(getDefaultInnerValue())
}
})
onMounted(() => {
setShowValue(false, false, true)
})
/**
* @description 根据传入的pickerpicker组件获取当前cell展示值。
*/
function getSelects(picker: 'before' | 'after') {
let value = picker === 'before' ? innerValue.value : endInnerValue.value
let selected: number[] = []
if (value) {
selected = getPickerValue(value, props.type, props.useSecond)
}
let selects = selected.map((value) => {
return {
[props.labelKey]: padZero(value),
[props.valueKey]: value
}
})
return selects
}
function noop() {}
function getDefaultInnerValue(isRegion?: boolean, isEnd?: boolean): string | number {
const { modelValue: value, defaultValue, maxDate, minDate, type } = props
if (isRegion) {
const index = isEnd ? 1 : 0
const targetValue = isArray(value) ? (value[index] as string) : ''
const targetDefault = isArray(defaultValue) ? (defaultValue[index] as string) : ''
const maxValue = type === 'time' ? dayjs(maxDate).format('HH:mm') : maxDate
const minValue = type === 'time' ? dayjs(minDate).format('HH:mm') : minDate
return targetValue || targetDefault || (isEnd ? maxValue : minValue)
} else {
return isDef(value || defaultValue) ? (value as string) || (defaultValue as string) : ''
}
}
// 对外暴露接口,打开弹框
function open() {
showPopup()
}
// 对外暴露接口,关闭弹框
function close() {
onCancel()
}
function showPopup() {
if (props.disabled || props.readonly) return
emit('open')
if (region.value) {
popupShow.value = true
showStart.value = true
innerValue.value = deepClone(getDefaultInnerValue(true, false))
endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
} else {
popupShow.value = true
innerValue.value = deepClone(getDefaultInnerValue())
}
setShowValue(true, false, true)
}
/**
* @description 区域选择时tab标签切换时触发
*/
function tabChange() {
showStart.value = !showStart.value
// 列项刷新多级联动挂载到datetimepickerView
const picker = showStart.value ? datetimePickerView.value : datetimePickerView1.value
picker!.setColumns(picker!.updateColumns())
emit('toggle', showStart.value ? innerValue.value : endInnerValue.value)
}
/**
* @description datetimePickerView change 事件
*/
function onChangeStart({ value }: { value: number | string }) {
if (!datetimePickerView.value) return
if (region.value && !datetimePickerView1.value) return
if (region.value) {
// 区间选择才需要处理边界值
// 区间选择才需要处理边界值
const currentArray = getPickerValue(value, props.type, props.useSecond)
const boundaryArray = getPickerValue(endInnerValue.value, props.type, props.useSecond)
const columns = datetimePickerView.value.getOriginColumns()
// 检查是否有任何列超出边界
const needsAdjust = columns.some((column, index) => {
return handleBoundaryValue(true, column.type, currentArray[index], currentArray, boundaryArray)
})
innerValue.value = deepClone(needsAdjust ? endInnerValue.value : value)
nextTick(() => {
showTabLabel.value = [setTabLabel(), deepClone(showTabLabel.value[1])]
emit('change', {
value: [innerValue.value, endInnerValue.value]
})
// 更新两个picker的列
datetimePickerView.value && datetimePickerView.value.setColumns(datetimePickerView.value.updateColumns())
datetimePickerView1.value && datetimePickerView1.value.setColumns(datetimePickerView1.value.updateColumns())
})
} else {
// 非区间选择直接设置值即可
innerValue.value = deepClone(value)
emit('change', {
value: innerValue.value
})
}
}
/**
* @description 区域选择 下方 datetimePickerView change 事件
*/
function onChangeEnd({ value }: { value: number | string }) {
if (!datetimePickerView.value || !datetimePickerView1.value) return
const currentArray = getPickerValue(value, props.type)
const boundaryArray = getPickerValue(innerValue.value, props.type)
const columns = datetimePickerView1.value.getOriginColumns()
// 检查是否有任何列超出边界
const needsAdjust = columns.some((column, index) => {
return handleBoundaryValue(false, column.type, currentArray[index], currentArray, boundaryArray)
})
endInnerValue.value = deepClone(needsAdjust ? innerValue.value : value)
nextTick(() => {
showTabLabel.value = [deepClone(showTabLabel.value[0]), setTabLabel(1)]
emit('change', {
value: [innerValue.value, endInnerValue.value]
})
// 更新两个picker的列
datetimePickerView.value && datetimePickerView.value.setColumns(datetimePickerView.value.updateColumns())
datetimePickerView1.value && datetimePickerView1.value.setColumns(datetimePickerView1.value.updateColumns())
})
}
/**
* @description 点击取消按钮触发。关闭popup触发cancel事件。
*/
function onCancel() {
popupShow.value = false
setTimeout(() => {
if (region.value) {
innerValue.value = deepClone(getDefaultInnerValue(true))
endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
} else {
innerValue.value = deepClone(getDefaultInnerValue())
}
}, 200)
emit('cancel')
}
/** picker触发confirm事件同步触发confirm事件 */
function onConfirm() {
if (props.loading || isLoading.value) return
// 如果当前还在滑动且未停止下来则锁住先不确认等滑完再自动确认避免pickview值未更新
if (isPicking.value) {
hasConfirmed.value = true
return
}
const { beforeConfirm } = props
if (beforeConfirm) {
beforeConfirm(
region.value ? [innerValue.value, endInnerValue.value] : innerValue.value,
(isPass: boolean) => {
isPass && handleConfirm()
},
proxy.$.exposed
)
} else {
handleConfirm()
}
}
function onPickStart() {
isPicking.value = true
}
function onPickEnd() {
isPicking.value = false
// 延迟一会,因为组件层级嵌套过多,日期的计算时间也较长
setTimeout(() => {
if (hasConfirmed.value) {
hasConfirmed.value = false
onConfirm()
}
}, 50)
}
function handleConfirm() {
if (props.loading || isLoading.value || props.disabled) {
popupShow.value = false
return
}
const value = region.value ? [innerValue.value, endInnerValue.value] : innerValue.value
popupShow.value = false
emit('update:modelValue', value)
emit('confirm', {
value
})
setShowValue(false, true)
}
/**
* @description 设置区域选择 tab 标签展示值
* @param {Number} index 索引标志位,有三个有效值; 0(默认):上方picker索引; 1:下方picker索引;
* @return {String} showTabLabel
*/
function setTabLabel(index: number = 0) {
if (region.value) {
let items: Record<string, any>[] = []
if (index === 0) {
items = ((datetimePickerView.value ? datetimePickerView.value!.getSelects() : undefined) ||
(innerValue.value && getSelects('before'))) as Record<string, any>[]
} else {
items = ((datetimePickerView1.value ? datetimePickerView1.value!.getSelects() : undefined) ||
(endInnerValue.value && getSelects('after'))) as Record<string, any>[]
}
return defaultDisplayFormat(items, true)
} else {
return ''
}
}
/**
* @description 设置展示值
* @param {Boolean} tab 是否修改tab展示值尽在区域选择情况下生效
* @param {Boolean} isConfirm 是否提交当前修改
*/
function setShowValue(tab: boolean = false, isConfirm: boolean = false, beforeMount: boolean = false) {
if (region.value) {
const items = beforeMount
? (innerValue.value && getSelects('before')) || []
: (datetimePickerView.value && datetimePickerView.value.getSelects && datetimePickerView.value.getSelects()) || []
const endItems = beforeMount
? (endInnerValue.value && getSelects('after')) || []
: (datetimePickerView1.value && datetimePickerView1.value.getSelects && datetimePickerView1.value.getSelects()) || []
showValue.value = tab
? showValue.value
: [
(props.modelValue as (string | number)[])[0] || isConfirm ? defaultDisplayFormat(items as Record<string, any>[]) : '',
(props.modelValue as (string | number)[])[1] || isConfirm ? defaultDisplayFormat(endItems as Record<string, any>[]) : ''
]
showTabLabel.value = [defaultDisplayFormat(items as Record<string, any>[], true), defaultDisplayFormat(endItems as Record<string, any>[], true)]
} else {
const items = beforeMount
? (innerValue.value && getSelects('before')) || []
: (datetimePickerView.value && datetimePickerView.value.getSelects && datetimePickerView.value.getSelects()) || []
showValue.value = deepClone(props.modelValue || isConfirm ? defaultDisplayFormat(items as Record<string, any>[]) : '')
}
}
/**
* @description 设置展示值
* @param {Object} items 获取到的选中项 包含 { value, label }
* @param {Boolean} tabLabel 当前返回的是否是展示tab上的标签
* @return {String} showValue / showTabLabel
*/
function defaultDisplayFormat(items: Record<string, any>[], tabLabel: boolean = false) {
if (items.length === 0) return ''
if (tabLabel && props.displayFormatTabLabel) {
return props.displayFormatTabLabel(items)
}
if (props.displayFormat) {
return props.displayFormat(items)
}
// 如果使用了自定义的的formatterdefaultDisplayFormat无效
if (props.formatter) {
const typeMaps = {
year: ['year'],
datetime: props.useSecond ? ['year', 'month', 'date', 'hour', 'minute', 'second'] : ['year', 'month', 'date', 'hour', 'minute'],
date: ['year', 'month', 'date'],
time: props.useSecond ? ['hour', 'minute', 'second'] : ['hour', 'minute'],
'year-month': ['year', 'month']
}
return items
.map((item, index) => {
return props.formatter!(typeMaps[props.type][index], item.value)
})
.join('')
}
switch (props.type) {
case 'year':
return items[0].label
case 'date':
return `${items[0].label}-${items[1].label}-${items[2].label}`
case 'year-month':
return `${items[0].label}-${items[1].label}`
case 'time':
return props.useSecond ? `${items[0].label}:${items[1].label}:${items[2].label}` : `${items[0].label}:${items[1].label}`
case 'datetime':
return props.useSecond
? `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}:${items[5].label}`
: `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}`
}
}
function setLoading(loading: boolean) {
isLoading.value = loading
}
function handleClear() {
emit('clear')
emit('update:modelValue', '')
setShowValue(false, true)
}
defineExpose<DatetimePickerExpose>({
open,
close,
setLoading
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>