Files
2026-09-24 16:25:22 +08:00

608 lines
17 KiB
Plaintext

<script setup lang="ts">
import { computed, ref, onMounted,watch,getCurrentInstance ,PropType} from "vue"
import { xDate } from "../../core/util/xDate.uts"
import { checkIsCssUnit,getUnit } from "../../core/util/xCoreUtil.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
import { xDateDayInfoType,xCalendarDateStyleStatusType } from "../../interface"
const i18n = xConfig.i18n;
/**
* @name 时间周 xWeekbar
* @page /pages/index/weekbar
* @category 导航组件
* @description 样式丰富,非常精美,能够适应不同设计要求.
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xWeekbar"})
const emits = defineEmits([
/**
* 时间选中时触发
* @param {string} date 当前时间
*/
'change',
/**
* 时间周切换时触发,比如滑动切换,切换周时触发
* @param {string[]} dates 当前时间组周一至周日的时间数组
*/
'swiperChange',
/**
* 等同v-model
* @param {string} date 当前时间
*/
'update:modelValue'
])
const props = defineProps({
/**
* 同步受控当前选中日期,空时不会自动选中。
*/
modelValue:{
type:String,
default:""
},
/**
* 开始的日期
*/
startDate: {
type: String,
default: "1900-1-1"
},
/**
* 结束的日期
*/
endDate: {
type: String,
default: "2100-1-1"
},
/**
* 格式展示在组件上的日期.
*/
format: {
type: String,
default: "DD"
},
/**
* 周上面显示的中文名称.'一', '二', '三', '四', '五', '六', '日'
* 由于有属性seekDay的原因这里的顺序你不允许自己排列,你只能按照0-6的顺序来排列。即周一至周日,内部会自动再排列以满足seekDay的值。
*/
cn: {
type: Array as PropType<string[]>,
default: () : string[] => [] as string[]
},
/**
* 你当前的一周的第一天的索引值是几:0: 周一,1: 周二,2: 周三,3: 周四,4: 周五,5: 周六,6: 周日
*/
seekDay:{
type: Number,
default: 0
},
/**
* 背景
*/
color: {
type: String,
default: "white"
},
/**
* 暗黑时的背景,如果不提供使用sheet暗黑背景
*/
darkColor: {
type: String,
default: ""
},
/**
* 字号颜色
*/
fontColor: {
type: String,
default: "#333333"
},
/**
* 字号颜色
*/
fontSize: {
type: String,
default: "14"
},
/**
* 暗黑时的字号颜色
*/
fontDarkColor: {
type: String,
default: "#fbfbfb"
},
/**
* 激活时的字号颜色,不区分暗黑
*/
fontActiveColor: {
type: String,
default: "white"
},
/**
* 选中的样式.
* rect和circular,none三种
*/
mode: {
type: String,
default: "rect"
},
/**
* mode为rect时的选中背景圆角
*/
rectRound: {
type: String,
default: "5"
},
/**
* 组件圆角
*/
round: {
type: String,
default: "0"
},
/**
* 激活时的背景,不区分暗黑,不填充默认是全局主题色
*/
activeBgColor: {
type: String,
default: ""
},
/**
* 上部分标题的高
*/
topHeight: {
type: String,
default: "32"
},
/**
* 下部分日期的高.
*/
bottomHeight: {
type: String,
default: "42"
},
/**
* 左右,上下的间隙
*/
padding: {
type: Array as PropType<string[]>,
default: () : string[] => ['4', '8'] as string[]
},
showAction: {
type: Boolean,
default: true
},
actionIcon: {
type: String,
default: 'arrow-left-s-line'
},
/**
* 操作栏的图标大小
*/
actionSize: {
type: String,
default: '24'
},
/**
* 操作栏的图标颜色
*/
actionColor: {
type: String,
default: '#bebebe'
},
/**
* 操作栏的图标暗黑颜色
*/
actionDarkColor: {
type: String,
default: '#bebebe'
},
/**
* 当前modelValue为空时,这里设置为false时,默认进来
* 不会选中当前日期.
*/
emptyValueSelected:{
type:Boolean,
default:true
},
/**
* 给日期设定状态类型为:xCalendarDateStyleStatusProps
*/
dateStatus:{
type:Array as PropType<xCalendarDateStyleStatusType[]>,
default:():xCalendarDateStyleStatusType[] => [] as xCalendarDateStyleStatusType[]
},
})
type XWEEKBAR_LISTTYPE = {
date : string,
xdate : xDate,
cnName : string,
disabled : boolean,
isCurrent : boolean,
isNowDay : boolean
}
const nowValue = ref(new xDate())
const tempNowValue = ref(new xDate())
const isClickDate = ref(false)
const isDateSelected = computed(():boolean=>(props.modelValue==''&&props.emptyValueSelected)||isClickDate.value||props.modelValue!='')
const _topHeight = computed(() : string => checkIsCssUnit(props.topHeight, xConfig.unit))
const _bottomHeight = computed(() : string => checkIsCssUnit(props.bottomHeight, xConfig.unit))
const _wrapTotalHeight = computed(():string=>{
let topHeight_s = checkIsCssUnit(props.topHeight, xConfig.unit);
let sizeNumber = parseInt(topHeight_s)
let bottomHeight_s = checkIsCssUnit(props.bottomHeight, xConfig.unit);
let sizeNumber2 = parseInt(bottomHeight_s)
return (sizeNumber+sizeNumber2).toString() + getUnit(topHeight_s)
})
const _rectRound = computed(() : string => checkIsCssUnit(props.rectRound, xConfig.unit))
const _round = computed(() : string => checkIsCssUnit(props.round, xConfig.unit))
const _cn = computed(() : string[] => {
if(props.cn.length>0) return props.cn
// 根据 seekDay 属性调整周名称的顺序
const weekNames = [
i18n!.t("tmui4x.weekbar.week",0),
i18n!.t("tmui4x.weekbar.week",1),
i18n!.t("tmui4x.weekbar.week",2),
i18n!.t("tmui4x.weekbar.week",3),
i18n!.t("tmui4x.weekbar.week",4),
i18n!.t("tmui4x.weekbar.week",5),
i18n!.t("tmui4x.weekbar.week",6),
]
// 如果 seekDay 为 0(默认周一),直接返回原数组
if (props.seekDay == 0) {
return weekNames
}
// 根据 seekDay 重新排列数组
const result: string[] = []
for (let i = 0; i < 7; i++) {
const index = (i + props.seekDay) % 7
result.push(weekNames[index])
}
return result
})
const _mode = computed(() : string => props.mode)
const _nowIndex = ref(0)
const _isCurrent = (date:xDate):boolean=>{
return date.isBetweenOf(nowValue.value, '=', 'd')
}
const setWeekDataToar = (dateParent:xDate,type:number):XWEEKBAR_LISTTYPE[]=>{
let date = dateParent.getClone().getDateStartOf('w')
// 根据 seekDay 调整一周的起始日期,确保所选日期落入该周
if (props.seekDay > 0) {
let start = date.getClone().add(props.seekDay, 'd')
// 如果偏移后的开始日在所选日期之后,则回退 7 天,保证包含所选日期
if (start.isBetweenOf(dateParent, '>', 'd')) {
start.subtraction(7, 'd')
}
date = start
}
if(type<0){
date.subtraction(1, 'w')
}else if(type>0){
date.add(1, 'w')
}
// 根据 seekDay 决定如何生成一周的日期
let dates: xDateDayInfoType[] = []
if (props.seekDay == 0) {
// 默认周一开始,使用原有的 getDaysOf('w')
dates = date.getDaysOf('w')
} else {
// 自定义周起始日,手动生成一周的日期
dates = []
for (let i = 0; i < 7; i++) {
let dayDate = date.getClone().add(i, 'd')
dates.push(dayDate.getDateInfo())
}
}
let startdate = new xDate(props.startDate)
let enddate = new xDate(props.endDate)
let listdata : XWEEKBAR_LISTTYPE[] = []
for (let i = 0; i < dates.length; i++) {
let el = dates[i]
let elDate = new xDate(el.date);
let d : XWEEKBAR_LISTTYPE = {
date: elDate.format(props.format),
xdate: elDate,
cnName: _cn.value[i] as string,
disabled: elDate.isBetweenOf(startdate, '<', 'd') || elDate.isBetweenOf(enddate, '>', 'd'),
isCurrent: elDate.isBetweenOf(nowValue.value, '=', 'd'),
isNowDay: elDate.isBetweenOf(new xDate(), '=', 'd'),
} as XWEEKBAR_LISTTYPE
listdata.push(d)
}
return listdata;
}
const _weeksAr = ref<XWEEKBAR_LISTTYPE[][]>([])
const getWeeksAr = ()=>{
let ars = [] as XWEEKBAR_LISTTYPE[][]
// 根据 seekDay 属性调整周数据的生成
if(_nowIndex.value==0){
ars.push(setWeekDataToar(tempNowValue.value,0))
ars.push(setWeekDataToar(tempNowValue.value,1))
ars.push(setWeekDataToar(tempNowValue.value,-1))
}else if(_nowIndex.value==1){
ars.push(setWeekDataToar(tempNowValue.value,-1))
ars.push(setWeekDataToar(tempNowValue.value,0))
ars.push(setWeekDataToar(tempNowValue.value,1))
}else if(_nowIndex.value==2){
ars.push(setWeekDataToar(tempNowValue.value,1))
ars.push(setWeekDataToar(tempNowValue.value,-1))
ars.push(setWeekDataToar(tempNowValue.value,0))
}
_weeksAr.value = [...ars]
}
// const _list = computed(() : XWEEKBAR_LISTTYPE[] => {
// return setWeekDataToar(nowValue.value,1);
// })
const _padding = computed(() : string[] => {
return [checkIsCssUnit(props.padding[0], xConfig.unit), checkIsCssUnit(props.padding[1], xConfig.unit)] as string[]
})
const _fotColor = computed(() : string => {
let color = props.fontColor;
if (xConfig.dark == 'dark') {
color = props.fontDarkColor;
}
return getDefaultColor(color)
})
const _showAction = computed(() : boolean => {
return props.showAction
})
const _activeFontColor = computed(() : string => {
return getDefaultColor(props.fontActiveColor)
})
const _activeBgColor = computed(() : string => {
let color = props.activeBgColor
if (color == '') {
color = xConfig.color
}
return getDefaultColor(color)
})
const _dateStatus = computed(() : xCalendarDateStyleStatusType[] => props.dateStatus)
const _bgColor = computed(() : string => {
let color = props.color
if (xConfig.dark == 'dark') {
color = props.darkColor;
if (color == '') {
color = xConfig.sheetDarkColor
}
}
return getDefaultColor(color)
})
const dateClick = (date : XWEEKBAR_LISTTYPE) => {
if(date.disabled) return;
nowValue.value = date.xdate
tempNowValue.value = date.xdate
emits('change', nowValue.value.format('YYYY-MM-DD'))
emits('update:modelValue', nowValue.value.format('YYYY-MM-DD'))
isClickDate.value = true;
}
const actionsClick = (type : string) => {
let startdate = new xDate(props.startDate)
let enddate = new xDate(props.endDate)
if (type == 'right') {
let disabled = nowValue.value.isBetweenOf(enddate, '>=', 'd')
if(disabled) return;
let xd = nowValue.value.getClone().add(1, 'd')
nowValue.value = xd;
tempNowValue.value =xd
} else {
let disabled = nowValue.value.isBetweenOf(startdate, '<=', 'd')
if(disabled) return;
let xd = nowValue.value.getClone().subtraction(1, 'd')
nowValue.value = xd;
tempNowValue.value =xd
}
getWeeksAr()
emits('change', nowValue.value.format('YYYY-MM-DD'))
emits('update:modelValue', nowValue.value.format('YYYY-MM-DD'))
}
watch(() : string => props.modelValue, (newval : string) => {
if (newval == '') return;
let newdate = new xDate(newval)
if (newdate.isBetweenOf(nowValue.value, '=', 'd')) return;
nowValue.value = newdate
tempNowValue.value = new xDate(newval)
_nowIndex.value = 0
getWeeksAr()
})
watch(
[
() : string => props.modelValue,
() : string => props.startDate,
() : string => props.endDate,
],
() => {
getWeeksAr()
}
)
function checkDataIsInDateStatus(date:xDate|null):string{
if(date == null) return '';
for(let k =0 ;k <_dateStatus.value.length;k++){
let itemStatus = _dateStatus.value[k]
let dates = itemStatus?.date??[];
let start = itemStatus?.between?.start??''
let end = itemStatus?.between?.end??''
let betweenColor = itemStatus?.between?.color??''
let notDates = itemStatus?.between?.notDate??[]
let nowDate = date!;
let isInBetweenDate = false;
if(start!=''&&end!=''){
let isBetween = nowDate.isBetween(new xDate(start),new xDate(end),'d','[]');
let isNotDate = false
for(let i=0;i<notDates.length;i++){
let item = notDates[i];
if(nowDate.isBetweenOf(new xDate(item),'=','d')){
isNotDate = true;
break;
}
}
isInBetweenDate = isBetween && !isNotDate
}
if(isInBetweenDate) return getDefaultColor(betweenColor==''?'primary':betweenColor)
let selfColor = ''
for(let i=0;i<dates.length;i++){
let item = dates[i];
if(nowDate.isBetweenOf(new xDate(item.date),'=','d')){
selfColor = item.color==''?'primary':item.color
break;
}
}
if(selfColor!=''){
return getDefaultColor(selfColor)
}
}
return ""
}
const swiperChange = (evt:SwiperChangeEvent)=>{
_nowIndex.value = evt.detail.current
tempNowValue.value = _weeksAr.value[evt.detail.current][0].xdate.getClone()
getWeeksAr()
const nowWeekDate:string[] = _weeksAr.value[_nowIndex.value].map((el:XWEEKBAR_LISTTYPE):string => el.xdate.format('YYYY-MM-DD'))
emits('swiperChange',nowWeekDate)
}
onMounted(() => {
if (props.modelValue != '') {
nowValue.value = new xDate(props.modelValue)
tempNowValue.value = new xDate(props.modelValue)
_nowIndex.value = 0
}
getWeeksAr()
})
</script>
<template>
<view class="xWeekBar"
:style="{backgroundColor:_bgColor,padding:`${_padding[1]} ${_showAction?'0px':_padding[0]}`,borderRadius:_round}">
<view @click="actionsClick('left')" v-if="_showAction" :style="{paddingLeft:_padding[0]}">
<!--
@slot 左边操作栏按钮
-->
<slot name="left">
<x-icon :font-size="actionSize" :color="props.actionColor" :dark-color="props.actionDarkColor"
:name="props.actionIcon"></x-icon>
</slot>
</view>
<view class="xWeekBarWrap">
<swiper :touchable="false" :disable-programmatic-animation="true" @change="swiperChange" :current="_nowIndex" :circular="true" :style="{height:_wrapTotalHeight}">
<swiper-item :style="{height:_wrapTotalHeight}" class="xWeekBarItemBox" v-for="(itemData,wrapIndex) in _weeksAr" :key="wrapIndex" >
<view class="xWeekBarItem" v-for="(item,index) in itemData" :key="index" @tap="dateClick(item)"
:style="{backgroundColor:(_isCurrent(item.xdate)&&_mode=='rect'&&isDateSelected?_activeBgColor:'transparent'),borderRadius:_rectRound}">
<view class="xWeekBarItemTop" :style="{height:_topHeight,opacity:item.disabled?'0.35':'1'}">
<x-text style="font-weight: bold;"
:color="_isCurrent(item.xdate)&&_mode!='circular'&&isDateSelected?_activeFontColor:_fotColor"
:dark-color="_isCurrent(item.xdate)&&_mode!='circular'&&isDateSelected?_activeFontColor:_fotColor"
:font-size="props.fontSize">{{item.cnName}}</x-text>
</view>
<view class="xWeekBarItemBottomWrap">
<view class="xWeekBarItemBottomWrapSelectedBox" :style="{
opacity:item.disabled?'0.35':'1',
height:_bottomHeight,
width:_bottomHeight,
borderRadius:_bottomHeight,
backgroundColor:_mode=='circular'&&_isCurrent(item.xdate)&&isDateSelected?_activeBgColor:'transparent'
}">
<x-text :color="_isCurrent(item.xdate)&&isDateSelected?_activeFontColor:_fotColor"
:dark-color="_isCurrent(item.xdate)&&isDateSelected?_activeFontColor:_fotColor" :font-size="props.fontSize"
:style="{opacity:_isCurrent(item.xdate)&&isDateSelected?'1':'0.7'}">{{item.date}}</x-text>
</view>
<view class="xCalendarViewStatus" :style="{backgroundColor:checkDataIsInDateStatus(item.xdate)}"></view>
</view>
</view>
</swiper-item>
</swiper>
</view>
<view @click="actionsClick('right')" v-if="_showAction" :style="{paddingRight:_padding[0]}">
<!--
@slot 右边操作栏按钮
-->
<slot name="right">
<x-icon :font-size="actionSize" :color="props.actionColor" :dark-color="props.actionDarkColor"
:rotation="180" :duration="0" :name="props.actionIcon"></x-icon>
</slot>
</view>
</view>
</template>
<style scoped>
.xCalendarViewStatus{
width:4px;
height:4px;
border-radius: 2px;
position: absolute;
bottom: 1px;
}
.xWeekBar {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.xWeekBarWrap {
flex: 1;
}
.xWeekBarItemBox{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.xWeekBarItem {
width: 14.2857143%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.xWeekBarItemTop {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.xWeekBarItemBottomWrap {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.xWeekBarItemBottomWrapSelectedBox {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
</style>