This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,683 @@
<script lang="ts">
import { type PropType,toRaw } from "vue"
import { getUid, setPagePullRefresh, getPagePullRefresh } from "../../core/util/xCoreUtil.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { xDate, xDateTypeTime, createDate } from "../../core/util/xDate.uts"
import { xConfig } from "../../config/xConfig.uts"
import { PICKER_ITEM_INFO } from "../../interface.uts"
type coverValue = {
value : string[][],
str : string
}
type ModelType = "year" | "month" | "day" | "hour" | "minute" | "second";
/**
* @name 日期选择器 xPickerDate
* @description 日期选择,可以控制显示精确到秒。默认的开始时间为当前时间的上一年,结束时间为默认当前时间
* 使用时,建议不要显示过多年份以防卡太多数据。
* @page /pages/index/picker-date
* @category 表单组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
export default {
data() {
let startValue = new xDate();
let endValue = new xDate();
startValue.subtraction(1, 'y')
return {
show: false,
nowValue: [] as string[][],
nowValueStr: '',
startDate: startValue,
endDate: endValue,
dateList: [] as PICKER_ITEM_INFO[][],
changeIndex: 0,
nowPull: false,
yanchiDuration: false
}
},
emits: [
/**
* 取消时触发
*/
'cancel',
/**
* 确认触发
* @param {string} date 当前选中时间id值
*/
'confirm',
/**
* 滑动变换时触发
* @param {string} date - 当前选中时间
*/
'change',
/**
* 变量控制打开状态
* 等同v-model:model-show
*/
'update:modelShow',
/**
* 经格式化后的值。等同v-model:model-str
*/
'update:modelStr',
'update:modelValue'
],
props: {
/**
* 当前时间,与modelStr不同,此提供的值必须是正常的时间格式
* 否则报错,无法运行。可以提供以下合法格式:
* YYYY,YYYY-MM,YYYY-MM-DD,YYYY-MM-DD HH,YYYY-MM-DD HH:mm,YYYY-MM-DD HH:mm:ss
*/
modelValue: {
type: String,
default: ""
},
/**
* 当前时间经过format格式化后输出的值。
* 此值不会处理输入,只输出显示。
*/
modelStr: {
type: String,
default: ""
},
/**
* 当前打开的状态。
* 等同v-model:model-show
*/
modelShow: {
type: Boolean,
default: false
},
/**
* 顶部标题
*/
title: {
type: String,
default: ""
},
/**
* 取消按钮的文本
*/
cancelText: {
type: String,
default: ""
},
/**
* 确认按钮的文本
*/
confirmText: {
type: String,
default: ""
},
/**
* 开始时间,请提供正确的时间格式
*/
start: {
type: String,
default: ""
},
/**
* 结束时间,请提供正确的时间格式
*/
end: {
type: String,
default: ""
},
/**
* 精确到的级别
* year:年
* month:年月
* day:年月日
* hour:年月日小时
* minute:年月日小时分钟
* second:年月日小时分钟秒
*/
type: {
type: String as PropType<ModelType>,
default: "day"
},
/**
* 输出时间格式,只对v-model:modelStr有效
* 如果桢同步对vmodel:modelValue有效需要设置formatSyncValue为true
* 有效格式:
* YYYY年
* MM月
* DD日
* hh小时
* mm分钟
* ss秒
*/
format: {
type: String,
default: "YYYY-MM-DD"
},
/**
* 是否将format格式化的v-model:modelStr同步到v-model:modelValue
* 默认false,注意:如果开启了同步,你要确保format的值是正常的时间值
* 正常兼容以下时间格式:
* YYYY,YYYY-MM,YYYY-MM-DD,YYYY-MM-DD HH,YYYY-MM-DD HH:mm,YYYY-MM-DD HH:mm:ss
*/
formatSyncValue:{
type:Boolean,
default:false
},
/**
* 上方的单位名称,'年', '月', '日', '时', '分', '秒'
*/
cellUnits: {
type: Array as PropType<string[]>,
default: () : string[] => [] as string[]
},
/**
* 是否懒加载内部内容。
* 当前你的列表内容非常多,且影响打开的动画性能时,请务必
* 设置此项为true,以获得流畅视觉效果。如果选择数据较少没有必要打开
* 要兼容微信就必须打开为true,非微信可以设置为false
*/
lazyContent: {
type: Boolean,
default: true
},
/**
* 层级
*/
zIndex:{
type: Number,
default: 1100
},
showClose:{
type: Boolean,
default: true
},
/**
* 是否禁用弹出
*/
disabled:{
type: Boolean,
default: false
},
/**
* 宽屏时是否让内容剧中显示
* 并限制其宽为屏幕宽,只展示中间内容以适应宽屏。
*/
widthCoverCenter: {
type: Boolean,
default: false
}
},
computed: {
_lazyContent() : boolean {
return this.lazyContent
},
_start_date() : xDate {
if (this.start == "") return this.startDate
return new xDate(this.start)
},
_end_date() : xDate {
if (this.end == "") return this.endDate
return new xDate(this.end)
},
_getDateType() : xDateTypeTime {
let isType = 's' as xDateTypeTime
if (this.type == 'year') isType = 'y'
if (this.type == 'month') isType = 'm'
if (this.type == 'day') isType = 'd'
if (this.type == 'hour') isType = 'h'
if (this.type == 'minute') isType = 'M'
return isType;
},
_disabled():boolean{
return this.disabled
},
_nowValueStr():string{
return this.stringArValuCoverToStringTemplate(this.modelValue)
},
_cellUnits():string[]{
if(this.cellUnits.length==0){
return [
this!.i18n.t("tmui4x.pickerDate.year"),
this!.i18n.t("tmui4x.pickerDate.month"),
this!.i18n.t("tmui4x.pickerDate.day"),
this!.i18n.t("tmui4x.pickerDate.hour"),
this!.i18n.t("tmui4x.pickerDate.minute"),
this!.i18n.t("tmui4x.pickerDate.second"),
]
}
return this.cellUnits;
},
_cancelText():string{
if(this.cancelText==''){
return this!.i18n.t("tmui4x.cancel")
}
return this.cancelText;
},
_confirmText():string{
if(this.confirmText==''){
return this!.i18n.t("tmui4x.confirm")
}
return this.confirmText;
},
_title():string{
if(this.title==''){
return this!.i18n.t("tmui4x.pickerTitle")
}
return this.title;
}
},
watch: {
modelValue(newvalue : string) {
if (newvalue == '') return;
let isType = this._getDateType
if (new xDate(newvalue).isBetweenOf(new xDate(this.nowValueStr), '=', isType)) return;
this.defaultModelvalue(newvalue, true)
},
modelShow(newValue : boolean) {
if (newValue == this.show) return;
this.show = newValue
}
},
mounted() {
this.yanchiDuration = this._lazyContent ? false : true
this.nowPull = getPagePullRefresh()
let nowValue = new xDate(this.modelValue)
this.defaultModelvalue(nowValue.format(), this.modelValue != '')
},
methods: {
defaultModelvalue(newvalue : string, showStr : boolean) {
const d = Date.now()
let isType = this._getDateType
let nowValue = new xDate(newvalue)
if (nowValue.isBetweenOf(this._start_date, '<=', isType)) {
nowValue = this._start_date;
}
if (nowValue.isBetweenOf(this._end_date, '>=', isType)) {
nowValue = this._end_date;
}
let stp = this.getRangByDateTime(nowValue)
this.nowValue = stp.value;
this.nowValueStr = stp.str;
this.dateList = this.getTimeTreeByStartAndEnd(this._start_date, this._end_date)
if (showStr) {
/**
* 经格式化后的值。等同v-model:model-str
*/
this.$emit('update:modelStr', this.formatTimeDate());
}
},
getRangByDateTime(d : xDate) : coverValue {
let nowRange = [
[d.getYear().toString()],
[(d.getMonth()).toString()],
[d.getDate().toString()],
[d.getHours().toString()],
[d.getMinutes().toString()],
[d.getSeconds().toString()],
] as string[][]
let nowRangeStr = d.getYear().toString() + "-" + (d.getMonth() + 1).toString()
+ "-" + d.getDate().toString()
+ " " + d.getHours().toString() + ":"
+ d.getMinutes().toString() + ":"
+ d.getSeconds().toString()
return {
value: nowRange as string[][],
str: nowRangeStr
} as coverValue
},
getNowTypeLenIndex() : number {
let index = 6
if (this.type == 'year') {
index = 1;
} else if (this.type == 'month') {
index = 2;
} else if (this.type == 'day') {
index = 3;
} else if (this.type == 'hour') {
index = 4;
} else if (this.type == 'minute') {
index = 5;
}
return index;
},
indexToHex(i : number) : string {
let n = i.toString()
if (n.length == 1) return '0' + n;
return n;
},
getTimeTreeByStartAndEnd(start : xDate, end : xDate) : PICKER_ITEM_INFO[][] {
let startCopy = start.getClone();
let nowDate = new xDate(this.nowValueStr)
let endCopy = end.getClone();
let years = [] as PICKER_ITEM_INFO[];
let months = [] as PICKER_ITEM_INFO[];
let days = [] as PICKER_ITEM_INFO[];
let hours = [] as PICKER_ITEM_INFO[];
let minutes = [] as PICKER_ITEM_INFO[];
let seconds = [] as PICKER_ITEM_INFO[];
for (let i = startCopy.getYear(); i <= endCopy.getYear(); i++) {
years.push({
id: i.toString(),
title: this.indexToHex(i)
} as PICKER_ITEM_INFO)
}
let _this = this;
function getD(type : string, s : number, n : number) {
if (type == 'm') {
for (let i = s; i <= n; i++) {
months.push({
id: i.toString(),
title: _this.indexToHex(i + 1)
} as PICKER_ITEM_INFO)
}
} else if (type == 'd') {
for (let i = s; i <= n; i++) {
days.push({
id: i.toString(),
title: _this.indexToHex(i)
} as PICKER_ITEM_INFO)
}
} else if (type == 'h') {
for (let i = s; i <= n; i++) {
hours.push({
id: i.toString(),
title: _this.indexToHex(i)
} as PICKER_ITEM_INFO)
}
} else if (type == 'M') {
for (let i = s; i <= n; i++) {
minutes.push({
id: i.toString(),
title: _this.indexToHex(i)
} as PICKER_ITEM_INFO)
}
} else if (type == 's') {
for (let i = s; i <= n; i++) {
seconds.push({
id: i.toString(),
title: _this.indexToHex(i)
} as PICKER_ITEM_INFO)
}
}
}
function getDnumber(type : xDateTypeTime, target : xDateTypeTime) : number[] {
let st = 0;
let et = 0
// 不包含起始, 开始和结束内。
if (nowDate.isBetween(startCopy, endCopy, type, '()')) {
if (target == 'm') {
st = 0
et = 11
} else if (target == 'd') {
st = 1
et = nowDate.getMonthCountDay()
} else if (target == 'h') {
st = 0
et = 23
} else if (target == 'M' || target == 's') {
st = 0
et = 59
}
//
} else {
// 开始和结束相等
if (startCopy.isBetweenOf(endCopy, '=', type)) {
if (target == 'm') {
st = startCopy.getMonth()
et = endCopy.getMonth()
} else if (target == 'd') {
st = startCopy.getDate()
et = endCopy.getDate()
} else if (target == 'h') {
st = startCopy.getHours()
et = endCopy.getHours()
} else if (target == 'M') {
st = startCopy.getMinutes()
et = endCopy.getMinutes()
} else if (target == 's') {
st = startCopy.getSeconds()
et = endCopy.getSeconds()
}
} else if (nowDate.isBetweenOf(startCopy, '<=', type)) {
if (target == 'm') {
st = startCopy.getMonth()
et = 11
} else if (target == 'd') {
st = startCopy.getDate()
et = startCopy.getMonthCountDay()
} else if (target == 'h') {
st = startCopy.getHours()
et = 23
} else if (target == 'M') {
st = startCopy.getMinutes()
et = 59
} else if (target == 's') {
st = startCopy.getSeconds()
et = 59
}
} else if (nowDate.isBetweenOf(endCopy, '>=', type)) {
if (target == 'm') {
st = 0
et = endCopy.getMonth()
} else if (target == 'd') {
st = 1
et = endCopy.getDate()
} else if (target == 'h') {
st = 0
et = endCopy.getHours()
} else if (target == 'M') {
st = 0
et = endCopy.getMinutes()
} else if (target == 's') {
st = 0
et = endCopy.getSeconds()
}
}
}
return [st, et] as number[]
}
let maxlen = this.getNowTypeLenIndex();
if (maxlen > 1) {
let sdate = getDnumber('y', 'm')
getD('m', sdate[0]!, sdate[1]!)
}
if (maxlen > 2) {
let sdate = getDnumber('m', 'd')
getD('d', sdate[0]!, sdate[1]!)
}
if (maxlen > 3) {
let sdate = getDnumber('d', 'h')
getD('h', sdate[0]!, sdate[1]!)
}
if (maxlen > 4) {
let sdate = getDnumber('h', 'M')
getD('M', sdate[0]!, sdate[1]!)
}
if (maxlen > 5) {
let sdate = getDnumber('M', 's')
getD('s', sdate[0]!, sdate[1]!)
}
return [years, months, days, hours, minutes, seconds].slice(0, this.getNowTypeLenIndex()) as PICKER_ITEM_INFO[][];
},
getRangNumber(start : number, end : number) : string[] {
let iar = [] as string[];
for (let i = start; i <= end; i++) {
iar.push(i.toString());
}
return iar;
},
stringArValuCoverToStringTemplate(newvalue:string) : string {
if(newvalue=='') return ''
let isType = this._getDateType
let nowValue = new xDate(newvalue)
if (nowValue.isBetweenOf(this._start_date, '<=', isType)) {
nowValue = this._start_date;
}
if (nowValue.isBetweenOf(this._end_date, '>=', isType)) {
nowValue = this._end_date;
}
let stp = this.getRangByDateTime(nowValue)
return new xDate(stp.str).format(this.format)
},
stringArValuCoverToString() : string {
if (this.nowValue.length != 6) return "";
let newsday = new xDate(this.nowValue[0][0] + "-" + (parseInt(this.nowValue[1][0]) + 1).toString() + "-1")
let days = parseInt(this.nowValue[2][0])
days = days >= newsday.getMonthCountDay() ? newsday.getMonthCountDay() : days
this.nowValue.splice(2, 1, [days.toString()])
return this.fillNumber(this.nowValue[0][0]) + "-" + this.fillNumber((parseInt(this.nowValue[1][0]) + 1).toString())
+ "-" + this.fillNumber(this.nowValue[2][0])
+ " " + this.fillNumber(this.nowValue[3][0]) + ":"
+ this.fillNumber(this.nowValue[4][0]) + ":"
+ this.fillNumber(this.nowValue[5][0])
},
fillNumber(n : string) : string {
if (parseInt(n) > 9) return n;
return "0" + n
},
formatTimeDate() : string {
if (this.nowValue.length != 6) return "";
let sp = this.format;
sp = sp.replace(/YYYY/g, this.fillNumber(this.nowValue[0][0]))
sp = sp.replace(/MM/g, this.fillNumber((parseInt(this.nowValue[1][0]) + 1).toString()))
sp = sp.replace(/DD/g, this.fillNumber(this.nowValue[2][0]))
sp = sp.replace(/hh/g, this.fillNumber(this.nowValue[3][0]))
sp = sp.replace(/mm/g, this.fillNumber(this.nowValue[4][0]))
sp = sp.replace(/ss/g, this.fillNumber(this.nowValue[5][0]))
return sp;
},
openShow() {
if(this._disabled) return;
this.show = true;
/**
* 变量控制打开状态
* 等同v-model:model-show
*/
this.$emit('update:modelShow', true)
setPagePullRefresh(false)
},
onClose() {
/**
* 变量控制打开状态
* 等同v-model:model-show
*/
this.$emit('update:modelShow', false)
this.cancelResetDataCol()
setPagePullRefresh(this.nowPull)
if (this._lazyContent) {
this.yanchiDuration = false
}
},
onOpen() {
this.yanchiDuration = true
},
mchange(ids : string[], index : number) {
this.nowValue.splice(index, 1, ids)
this.nowValueStr = this.stringArValuCoverToString()
/**
* 滑动变换时触发
* @param {string} date 当前选中时间
*/
this.$emit('change', this.nowValueStr)
this.dateList = this.getTimeTreeByStartAndEnd(this._start_date, this._end_date)
this.$forceUpdate()
},
onCancel() {
this.$emit('cancel')
this.cancelResetDataCol()
},
cancelResetDataCol() {
let stp = this.getRangByDateTime(new xDate(this.modelValue))
this.nowValue = stp.value;
this.nowValueStr = stp.str;
this.dateList = this.getTimeTreeByStartAndEnd(this._start_date, this._end_date)
},
onConfirm() {
this.nowValueStr = this.stringArValuCoverToString()
const syncValue:string = this.formatSyncValue?this.formatTimeDate():(toRaw(this.nowValueStr) as string)
/**
* 点击确认时同步。等同v-model
*/
this.$emit('update:modelValue', syncValue);
/**
* 经格式化后的值。等同v-model:model-str
*/
this.$emit('update:modelStr', this.formatTimeDate());
this.$emit('confirm', syncValue);
}
},
}
</script>
<template>
<view @click="openShow">
<!--
@slot 插槽,默认触发打开选择器。你的默认布局可以放置在这里。
@prop {string} label - 当前选中的字符串
-->
<slot :label="_nowValueStr"></slot>
</view>
<x-drawer
:lazy="_lazyContent"
:cancel-text="_cancelText"
:confirm-text="_confirmText"
:zIndex="zIndex" @open="onOpen"
:widthCoverCenter="widthCoverCenter"
:disabledScroll="true"
:title="_title"
@close="onClose"
@confirm="onConfirm"
@cancel="onCancel"
:showFooter="true"
v-model:show="show"
:show-close="showClose"
size="410">
<view class="xPickerDateWrap">
<x-picker-view v-if="yanchiDuration" :cellUnits="[_cellUnits[index]]" @change="mchange($event as string[],index)"
:model-value="nowValue[index]" v-for="(item,index) in dateList" :key="index" style="flex: 1;"
:list="item"></x-picker-view>
</view>
<x-loading v-if="!yanchiDuration"></x-loading>
</x-drawer>
</template>
<style scoped>
.xPickerDateWrap{
display: flex;
flex-direction: row;
justify-content: flex-start;
}
</style>