2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
@@ -0,0 +1,2 @@
export * from './week-calendar-custom'
export * from './use-week-calendar'
@@ -0,0 +1,57 @@
import { getCurrentInstance, nextTick, onMounted, ref } from 'vue'
import { useSelectorQuery } from '../../../../hooks'
import { debugWarn, generateId } from '../../../../utils'
export const useWeekCalendarSelector = () => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnWeekCalendar', '请在 setup 中使用 useWeekCalendarSelector')
}
const componentDateItemId = `twcdi-${generateId()}`
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 存放日期节点容器的高度
const dateItemContainerHeight = ref<number>(0)
let initCount = 0
// 获取日期节点信息
const getDateItemNodeInfo = async () => {
try {
const nodeInfo = await getSelectorNodeInfo(`#${componentDateItemId}-0-0`)
dateItemContainerHeight.value = nodeInfo.height || 0
dateItemContainerHeight.value += uni.upx2px(16)
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnWeekCalendar', `获取日期节点信息失败:${err}`)
return
}
initCount++
setTimeout(() => {
getDateItemNodeInfo()
}, 150)
}
}
onMounted(() => {
// #ifndef APP-PLUS || H5 || MP-ALIPAY
nextTick(() => {
getDateItemNodeInfo()
})
// #endif
// #ifdef APP-PLUS || H5 || MP-ALIPAY
setTimeout(() => {
getDateItemNodeInfo()
}, 150)
// #endif
})
return {
componentDateItemId,
dateItemContainerHeight,
}
}
@@ -0,0 +1,220 @@
import { computed, nextTick, ref, watch } from 'vue'
import dayjs from '../../../../libs/dayjs'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../../constants'
import { isEmptyVariableInDefault } from '../../../../utils'
import { useWeekCalendarSelector } from './use-week-calendar-selector'
import type { SetupContext } from 'vue'
import type { WeekCalendarEmits, WeekCalendarProps } from '../week-calendar'
import type {
WeekCalanderDateStatus,
WeekCalendarData,
WeekCalendarItem,
} from '../types'
export const useWeekCalendar = (
props: WeekCalendarProps,
emits: SetupContext<WeekCalendarEmits>['emit']
) => {
// 当前日期的Dayjs
const currentDateDayjs = dayjs()
const { componentDateItemId, dateItemContainerHeight } =
useWeekCalendarSelector()
// 当前日期的年份
const currentYear = computed<number>(() =>
props.year ? Number(props.year) : currentDateDayjs.year()
)
// 当前日期的月份
const currentMonth = computed<number>(() =>
props.month ? Number(props.month) : currentDateDayjs.month() + 1
)
// 最小可选日期
const minDate = computed<number>(() =>
props.minDate
? Number(props.minDate)
: currentDateDayjs.year() === currentYear.value &&
currentDateDayjs.month() + 1 === currentMonth.value
? currentDateDayjs.date()
: 1
)
// 最大可选日期
const maxDate = computed<number>(() =>
props.maxDate ? Number(props.maxDate) : currentDateDayjs.daysInMonth()
)
// 用户自定义日期描述数据
const customDescData = computed<Map<number, string>>(() => {
const map = new Map<number, string>()
props.customData.forEach((item) => {
map.set(item.date, item.desc)
})
return map
})
// 星期提示文字
const weekText = ref<string[]>(['日', '一', '二', '三', '四', '五', '六'])
// 周日历数据
const weekCalendarData = ref<WeekCalendarData>([])
// 当前激活的日期
const activeDate = computed<number>(() =>
isEmptyVariableInDefault(props.modelValue, 0)
)
// 当前选中的轮播位置
const currentSwiperIndex = ref<number>(0)
// 更新指定的日期为激活状态
const updateActiveDate = (date: number) => {
weekCalendarData.value.forEach((week) => {
week.forEach((item) => {
if (item.status !== 'disabled') {
if (item.date === date) {
item.status = 'active'
} else {
item.status = 'normal'
}
}
})
})
}
// 更新modelValue的值
const updateModelValue = (value: number, changeEmits = true) => {
emits(UPDATE_MODEL_EVENT, value)
if (changeEmits) {
updateActiveDate(value)
nextTick(() => {
emits(CHANGE_EVENT, value)
})
}
}
// 生成周日历数据
const generateWeekCalendarData = () => {
const data: WeekCalendarItem[] = []
const generateMonthDayjs = dayjs(
`${currentYear.value}/${currentMonth.value}/01`
)
const dates = generateMonthDayjs.daysInMonth()
const firstDayWeek = generateMonthDayjs.day()
// 填充空白数据
for (let i = 0; i < firstDayWeek; i++) {
data.push({
date: 0,
status: 'disabled',
})
}
// 填充日期数据
for (let i = 1; i <= dates; i++) {
let status: WeekCalanderDateStatus =
i >= minDate.value && i <= maxDate.value ? 'normal' : 'disabled'
if (i === activeDate.value) {
status = 'active'
// 设置当前激活日期所在的位置
currentSwiperIndex.value = Math.floor((i + firstDayWeek - 1) / 7)
}
const desc = customDescData.value.get(i)
data.push({
date: i,
status,
desc,
})
}
// 分割数据,每7个为一组
const result: WeekCalendarData = []
for (let i = 0; i < data.length; i += 7) {
result.push(data.slice(i, i + 7))
}
weekCalendarData.value = result
}
// 初始化周日历数据
const initWeekCalendarData = () => {
let updateModelValueDate = false
let modelValue = props.modelValue
// 判断modelValue是否有值,并且在最小和最大日期范围内
// 如果没有值则以当前日期为准
if (!modelValue) {
modelValue = currentDateDayjs.date()
updateModelValueDate = true
}
// 如果比最小日期小,则以最小日期为准
if (modelValue < minDate.value) {
modelValue = minDate.value
updateModelValueDate = true
}
// 如果比最大日期大,则以最大日期为准
if (modelValue > maxDate.value) {
modelValue = maxDate.value
updateModelValueDate = true
}
if (updateModelValueDate) {
updateModelValue(modelValue)
}
nextTick(() => {
generateWeekCalendarData()
})
}
initWeekCalendarData()
// 日期选中事件
const dateItemClick = (item: WeekCalendarItem) => {
if (
item.status === 'active' ||
item.status === 'disabled' ||
item.date === 0
)
return
updateModelValue(item.date)
}
// 切换星期
const switchWeek = (type: 'prev' | 'next') => {
if (type === 'prev') {
if (currentSwiperIndex.value === 0) return
currentSwiperIndex.value--
}
if (type === 'next') {
if (currentSwiperIndex.value === weekCalendarData.value.length - 1) return
currentSwiperIndex.value++
}
emits('week-change', currentSwiperIndex.value)
}
// 滑动切换星期
const swiperChangeWeek = (event: any) => {
currentSwiperIndex.value = event.detail.current
emits('week-change', currentSwiperIndex.value)
}
// 如果自定义数据发生变化,重新生成周日历数据
watch(
() => props.customData,
() => {
generateWeekCalendarData()
},
{
deep: true,
}
)
return {
componentDateItemId,
dateItemContainerHeight,
weekCalendarData,
weekText,
currentSwiperIndex,
dateItemClick,
switchWeek,
swiperChangeWeek,
}
}
@@ -0,0 +1,63 @@
import { computed, toRef } from 'vue'
import { useComponentColor, useNamespace } from '../../../../hooks'
import type { CSSProperties } from 'vue'
import type { WeekCalendarProps } from '../week-calendar'
import type { WeekCalanderDateStatus } from '../types'
type WeekCalendarItemClass = (status: WeekCalanderDateStatus) => string
type WeekCalendarItemStyle = (status: WeekCalanderDateStatus) => CSSProperties
export const useWeekCalendarCustomStyle = (props: WeekCalendarProps) => {
const ns = useNamespace('week-calendar')
// 解析颜色
const [activeBgColorClass, activeBgColorStyle] = useComponentColor(
toRef(props, 'activeBgColor'),
'bg'
)
const [activeTextColorClass, activeTextColorStyle] = useComponentColor(
toRef(props, 'activeTextColor'),
'text'
)
// dateItem对应的类
const itemClass = computed<WeekCalendarItemClass>(() => {
return (status: WeekCalanderDateStatus) => {
const cls: string[] = [ns.is(status)]
if (status === 'active') {
if (activeBgColorClass.value) cls.push(activeBgColorClass.value)
if (activeTextColorClass.value) cls.push(activeTextColorClass.value)
}
return cls.join(' ')
}
})
// dateItem的样式
const itemStyle = computed<WeekCalendarItemStyle>(() => {
return (status: WeekCalanderDateStatus) => {
const style: CSSProperties = {}
if (status === 'active') {
if (!activeBgColorClass.value)
style.backgroundColor =
activeBgColorStyle.value || 'var(--tn-color-primary)'
if (activeTextColorStyle.value) {
style.color = activeTextColorStyle.value
} else if (!activeBgColorClass.value && !activeTextColorClass.value) {
style.color = 'var(--tn-color-white)'
}
}
return style
}
})
return {
ns,
itemClass,
itemStyle,
}
}
@@ -0,0 +1,3 @@
import type WeekCalendar from './week-calendar.vue'
export type TnWeekCalendarInstance = InstanceType<typeof WeekCalendar>
@@ -0,0 +1,9 @@
export type WeekCalanderDateStatus = 'active' | 'normal' | 'disabled'
export type WeekCalendarData = Array<Array<WeekCalendarItem>>
export interface WeekCalendarItem {
date: number
status: WeekCalanderDateStatus
desc?: string
}
@@ -0,0 +1,64 @@
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../constants'
import { buildProps, definePropType, isNumber } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export interface WeekCalendarCustomData {
date: number
desc: string
}
export const weekCalendarProps = buildProps({
/**
* @description 绑定月份选中日期的值
*/
modelValue: Number,
/**
* @description 选中时的背景颜色,以tn开头使用图鸟内置的颜色
*/
activeBgColor: String,
/**
* @description 选中时的文字颜色,以tn开头使用图鸟内置的颜色
*/
activeTextColor: String,
/**
* @description 绑定的年份,如果为空则使用当前年份
*/
year: {
type: [String, Number],
},
/**
* @description 绑定的月份,如果为空则使用当前月份
*/
month: {
type: [String, Number],
},
/**
* @description 最小允许选择月份的日期
*/
minDate: {
type: [String, Number],
},
/**
* @description 最大允许选择月份的日期
*/
maxDate: {
type: [String, Number],
},
/**
* @description 自定义数据
*/
customData: {
type: definePropType<Array<WeekCalendarCustomData>>(Array),
default: () => [],
},
})
export const weekCalendarEmits = {
[UPDATE_MODEL_EVENT]: (value: number) => isNumber(value),
[CHANGE_EVENT]: (value: number) => isNumber(value),
'week-change': (value: number) => isNumber(value),
}
export type WeekCalendarProps = ExtractPropTypes<typeof weekCalendarProps>
export type WeekCalendarEmits = typeof weekCalendarEmits
@@ -0,0 +1,106 @@
<script lang="ts" setup>
import TnIcon from '../../icon/src/icon.vue'
import { weekCalendarEmits, weekCalendarProps } from './week-calendar'
import { useWeekCalendar, useWeekCalendarCustomStyle } from './composables'
const props = defineProps(weekCalendarProps)
const emits = defineEmits(weekCalendarEmits)
const {
componentDateItemId,
dateItemContainerHeight,
weekCalendarData,
weekText,
currentSwiperIndex,
dateItemClick,
switchWeek,
swiperChangeWeek,
} = useWeekCalendar(props, emits)
const { ns, itemClass, itemStyle } = useWeekCalendarCustomStyle(props)
</script>
<template>
<view :class="[ns.b()]">
<!-- 星期中文数据 -->
<view :class="[ns.e('weeks')]">
<view
v-for="(item, index) in weekText"
:key="index"
:class="[ns.e('week')]"
>
{{ item }}
</view>
</view>
<!-- 周日历数据 -->
<view
:class="[ns.e('data')]"
:style="{
height: `${
dateItemContainerHeight ? `${dateItemContainerHeight}px` : 'auto'
}`,
}"
>
<!-- 星期切换 -->
<view
v-if="currentSwiperIndex > 0"
class="left"
:class="[ns.e('data__week-btn')]"
@tap.stop="switchWeek('prev')"
>
<TnIcon name="left" />
</view>
<view
v-if="currentSwiperIndex < weekCalendarData.length - 1"
class="right"
:class="[ns.e('data__week-btn')]"
@tap.stop="switchWeek('next')"
>
<TnIcon name="right" />
</view>
<swiper
:class="[ns.e('data__swiper')]"
:indicator-dots="false"
:autoplay="false"
:circular="false"
:current="currentSwiperIndex"
@change="swiperChangeWeek"
>
<swiper-item
v-for="(item, index) in weekCalendarData"
:key="index"
:class="[ns.e('data__swiper-item')]"
>
<view :class="[ns.e('data__dates')]">
<template v-for="(dateItem, dateIndex) in item" :key="dateIndex">
<view
:id="`${componentDateItemId}-${index}-${dateIndex}`"
:class="[ns.e('data__date')]"
@tap.stop="dateItemClick(dateItem)"
>
<view
class="date"
:class="[itemClass(dateItem.status)]"
:style="itemStyle(dateItem.status)"
>
<view v-if="dateItem.date" class="date-value">
<view class="value">
{{ dateItem.date }}
</view>
<view class="desc">
{{ dateItem.desc }}
</view>
</view>
</view>
</view>
</template>
</view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/week-calendar.scss';
</style>