608 lines
17 KiB
Plaintext
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> |