<template>
<view @click="handleClick">
<slot></slot>
</view>
</template>
<script setup lang="uts">
import { FORM_KEY } from '../common.uts'
import { UniPickerElement } from './global.uts'
import { FormContext } from '../types.uts'
const uuid = `${Date.now()}${Math.floor(Math.random() * 1e7)}`
const baseEventName = `uni_picker_${uuid}`
const readyEventName = `${baseEventName}_ready`
const optionsEventName = `${baseEventName}_options`
const selectorChangeEventName = `${baseEventName}_selector_change`
const cancelEventName = `${baseEventName}_cancel_change`
const multiSelectorChangeEventName = `${baseEventName}_multiSelector_change`
const timeChangeEventName = `${baseEventName}_time_change`
const columnChangeEventName = `${baseEventName}_column_change`
type UniPickerCancelEventDetail = {}
type UniPickerChangeEventDetail = {
value : number | string | number[]
}
type UniPickerColumnChangeEventDetail = {
value : number
column : number
}
/**
* 取消事件触发
*/
class UniPickerCancelEvent extends UniCustomEvent<UniPickerCancelEventDetail> {
constructor() {
super('cancel', {
detail: {}
} as UniCustomEventOptions<UniPickerCancelEventDetail>)
}
}
/**
* change 事件触发
*/
class UniPickerChangeEvent extends UniCustomEvent<UniPickerChangeEventDetail> {
constructor(value : number | string | number[]) {
super('change', {
detail: { value } as UniPickerChangeEventDetail
} as UniCustomEventOptions<UniPickerChangeEventDetail>)
}
}
/**
* 列改变 columnchange 事件触发
*/
class UniPickerColumnChangeEvent extends UniCustomEvent<UniPickerColumnChangeEventDetail> {
constructor(column : number, value : number) {
super('columnchange', {
detail: { value, column } as UniPickerColumnChangeEventDetail
} as UniCustomEventOptions<UniPickerColumnChangeEventDetail>)
}
}
type Mode = 'selector' | 'multiSelector' | 'time' | 'date'
type Fields = 'year' | 'month' | 'day'
defineOptions({
name: 'picker',
// @ts-ignore
rootElement: {
class: UniPickerElement
}
})
interface PickerProps {
/**
* 是否禁用
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
disabled ?: boolean
/**
* 选择器类型
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
mode ?: Mode
/**
* mode为 selector 或 multiSelector 时,range 有效
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
range ?: string[]
/**
* 当 range 是一个 Object Array 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
rangeKey ?: string
/**
* 表示选择了 range 中的第几个(下标从 0 开始)
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
value ?: number | string | number[]
/**
* 表示有效时间范围的开始
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
start ?: string
/**
* 表示有效时间范围的结束
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
end ?: string
/**
* 有效值 year,month,day,表示选择器的粒度
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
fields ?: Fields
/**
* 表单的控件名称,作为键值对的一部分与表单(form组件)一同提交
* @uniPlatform {
* "app": {
* "harmony": {
* "unixvVer": "5.0"
* }
* }
* }
*/
name ?: string
}
const props = withDefaults(defineProps<PickerProps>(), {
disabled: false,
mode: 'selector',
range: [],
rangeKey: '',
value: '',
start: '',
end: '',
fields: 'day',
name: ''
})
const emit = defineEmits<{
change : UniPickerChangeEvent
columnchange : UniPickerColumnChangeEvent
cancel : UniPickerCancelEvent
}>()
const formCtx = inject<FormContext | null>(FORM_KEY, null)
const innerValue = ref<any>(props.value)
const initialValue = ref<any>('')
const isOpening = ref(false)
const OPEN_LOCK_DURATION = 300
const DEFAULT_START_YEAR = 1970
const DEFAULT_END_YEAR = 2099
watch(() => props.value, (value) => {
innerValue.value = value
// 解决数据更新视图不更新的问题
if (props.mode == 'multiSelector') {
uni.$emit(optionsEventName, {
range: props.range,
rangeKey: props.rangeKey,
multiSelectorValue: value ?? []
})
}
}, { deep: true })
function handleClick() {
if (props.disabled) {
return
}
// 防止重复打开
if (isOpening.value) {
return
}
isOpening.value = true
uni.openDialogPage({
url: `/uni_modules/uni-picker-dom2/pages/picker/picker?readyEventName=${readyEventName}&optionsEventName=${optionsEventName}&cancelEventName=${cancelEventName}&selectorChangeEventName=${selectorChangeEventName}&multiSelectorChangeEventName=${multiSelectorChangeEventName}&timeChangeEventName=${timeChangeEventName}&columnChangeEventName=${columnChangeEventName}`,
success() {
setTimeout(() => {
isOpening.value = false
}, OPEN_LOCK_DURATION)
},
fail() {
isOpening.value = false
uni.$off(readyEventName)
uni.$off(optionsEventName)
}
})
}
function sendOptions(eventName : string) {
const baseParams = {
mode: props.mode,
}
switch (props.mode) {
case 'selector':
baseParams.range = props.range
baseParams.rangeKey = props.rangeKey
baseParams.selectorValue = props.value ?? 0
break
case 'multiSelector':
baseParams.range = props.range
baseParams.rangeKey = props.rangeKey
baseParams.multiSelectorValue = props.value ?? []
break
case 'time':
baseParams.timeValue = getCurrentTimeValue()
baseParams.start = parseStartAndEndTime('start')
baseParams.end = parseStartAndEndTime('end')
break
case 'date':
baseParams.dateValue = getCurrentDateValue()
baseParams.fields = props.fields
baseParams.start = parseStartAndEndDate('start')
baseParams.end = parseStartAndEndDate('end')
}
uni.$emit(eventName, baseParams)
}
onMounted(() => {
initialValue.value = props.value
// register with form if available
if (formCtx && props.name) {
formCtx.registerField({
name: props.name,
getValue: () => innerValue.value,
reset: () => { innerValue.value = initialValue.value }
})
}
// 监听页面加载
uni.$on(readyEventName, () => {
sendOptions(optionsEventName)
})
// 监听取消按钮
uni.$on(cancelEventName, () => {
emit('cancel', new UniPickerCancelEvent())
})
// 单列change触发
uni.$on(selectorChangeEventName, (value : number) => {
innerValue.value = value
emit('change', new UniPickerChangeEvent(value))
})
// 多列change触发
uni.$on(multiSelectorChangeEventName, (value : number[]) => {
innerValue.value = value
emit('change', new UniPickerChangeEvent(value))
})
// 日期时间change触发
uni.$on(timeChangeEventName, (value : string) => {
innerValue.value = value
emit('change', new UniPickerChangeEvent(value))
})
// 列滚动columnchange 触发
uni.$on(columnChangeEventName, (column : number, value : number) => {
emit('columnchange', new UniPickerColumnChangeEvent(column, value))
})
})
onUnmounted(() => {
if (formCtx && props.name) {
formCtx.unregisterField(props.name)
}
uni.$off(readyEventName)
uni.$off(selectorChangeEventName)
uni.$off(multiSelectorChangeEventName)
uni.$off(timeChangeEventName)
uni.$off(columnChangeEventName)
uni.$off(cancelEventName)
})
function getCurrentDateValue() {
const today = parseTime(new Date())
let date = props.value
let date_data : string[] = []
if (date != '' && date != null) {
let date_val = date.split('-') as string[]
while (date_val.length < 3) {
date_val.push('1');
}
const year = Number(date_val[0]);
const startYear = props.start ? Number(props.start.split('-')[0]) : DEFAULT_START_YEAR;
const endYear = props.end ? Number(props.end.split('-')[0]) : DEFAULT_END_YEAR;
if (year < startYear) {
date_val[0] = String(startYear);
} else if (year > endYear) {
date_val[0] = String(endYear);
}
try {
const year = Number(date_val[0])
const month = Number(date_val[1])
const day = Number(date_val[2])
date_data = parseTime(new Date(year, month - 1, day))
} catch (_) {
date_data = today
}
} else {
date_data = today
}
return date_data
}
function getCurrentTimeValue() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
// 格式化为 ['02', '10']
const timeArray = [hours, minutes];
let time = props.value
let val : string[]
if (time) {
let timeVal = time.split(':')
if (time.length < 2) {
val = timeArray
} else {
val = formatTime(timeVal)
}
} else {
val = timeArray
}
return val
}
function formatTime(time : string[]) {
const formattedHours = String(time[0]).padStart(2, '0');
const formattedMinutes = String(time[1]).padStart(2, '0');
return [formattedHours, formattedMinutes]
}
function parseTime(date : Date) : string[] {
// 格式化为YY-MM-DD(两位数补零)
const year = (date.getFullYear()).toString().padStart(2, '0'); // 取年份后两位
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 月份范围1-12
const day = date.getDate().toString().padStart(2, '0');
return [year, month, day]
}
function parseStartAndEndTime(type : 'start' | 'end') {
let time = props[type]
let defaultVal = ['00', '00']
if (type == 'end') {
defaultVal = ['23', '59']
}
let val : string[]
if (time) {
let timeVal = time.split(':')
if (time.length < 2) {
val = defaultVal
} else {
val = formatTime(timeVal)
}
} else {
return defaultVal
}
return val
}
function parseStartAndEndDate(type : 'start' | 'end') : string[] {
const dateStr = props[type]
// 默认值设置
const defaultDate = type == 'start'
? [String(DEFAULT_START_YEAR), '01', '01'] // 起始默认日期
: [String(DEFAULT_END_YEAR), '12', '31']; // 结束默认日期
// 空值直接返回默认
if (dateStr == null || dateStr.length == 0) return defaultDate;
// 分割日期字符串
const parts = dateStr.split('-');
if (parts.length != 3) return defaultDate;
// 格式化各部分(补零 + 截取)
const formatPart = (value : string, length : number) => {
return value.padStart(length, '0').slice(0, length);
};
const year = formatPart(parts[0], 4); // 年份补足4位
const month = formatPart(parts[1], 2); // 月份补足2位
const day = formatPart(parts[2], 2); // 日期补足2位
return [year, month, day];
}
</script>