修改日历组件
This commit is contained in:
@@ -1,117 +0,0 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
|
||||||
import { View, Text, Image } from '@tarojs/components'
|
|
||||||
import styles from './index.module.scss'
|
|
||||||
import images from '@/config/images'
|
|
||||||
interface CalendarCardProps {
|
|
||||||
value?: Date
|
|
||||||
minDate?: Date
|
|
||||||
maxDate?: Date
|
|
||||||
onChange?: (date: Date) => void
|
|
||||||
onNext?: (date: Date) => void
|
|
||||||
onHeaderClick?: (date: Date) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1)
|
|
||||||
const endOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth() + 1, 0)
|
|
||||||
const addMonths = (date: Date, delta: number) => new Date(date.getFullYear(), date.getMonth() + delta, 1)
|
|
||||||
|
|
||||||
const formatHeader = (date: Date) => `${date.getMonth() + 1}月 ${date.getFullYear()}`
|
|
||||||
|
|
||||||
const CalendarCard: React.FC<CalendarCardProps> = ({
|
|
||||||
value,
|
|
||||||
minDate,
|
|
||||||
maxDate,
|
|
||||||
onChange,
|
|
||||||
onHeaderClick
|
|
||||||
}) => {
|
|
||||||
const today = new Date()
|
|
||||||
const [current, setCurrent] = useState<Date>(value || startOfMonth(today))
|
|
||||||
const [selected, setSelected] = useState<Date>(value || today)
|
|
||||||
|
|
||||||
|
|
||||||
const firstDay = useMemo(() => startOfMonth(current), [current])
|
|
||||||
const lastDay = useMemo(() => endOfMonth(current), [current])
|
|
||||||
|
|
||||||
const days = useMemo(() => {
|
|
||||||
const startWeekday = firstDay.getDay() // 0 周日
|
|
||||||
const prevPadding = startWeekday // 周日为第一列
|
|
||||||
const total = prevPadding + lastDay.getDate()
|
|
||||||
const rows = Math.ceil(total / 7)
|
|
||||||
const grid: (Date | null)[] = []
|
|
||||||
for (let i = 0; i < rows * 7; i++) {
|
|
||||||
const day = i - prevPadding + 1
|
|
||||||
if (day < 1 || day > lastDay.getDate()) {
|
|
||||||
grid.push(null)
|
|
||||||
} else {
|
|
||||||
grid.push(new Date(current.getFullYear(), current.getMonth(), day))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return grid
|
|
||||||
}, [firstDay, lastDay, current])
|
|
||||||
|
|
||||||
const isDisabled = (d: Date) => {
|
|
||||||
if (minDate && d < minDate) return true
|
|
||||||
if (maxDate && d > maxDate) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const gotoMonth = (delta: number) => setCurrent(prev => addMonths(prev, delta))
|
|
||||||
|
|
||||||
const handleHeaderClick = () => {
|
|
||||||
onHeaderClick && onHeaderClick(current)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelectDay = (d: Date | null) => {
|
|
||||||
if (!d || isDisabled(d)) return
|
|
||||||
setSelected(d)
|
|
||||||
onChange && onChange(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View className={styles['calendar-card']}>
|
|
||||||
<View className={styles['header']}>
|
|
||||||
<View className={styles['header-left']} onClick={handleHeaderClick}>
|
|
||||||
<Text className={styles['header-text']}>{formatHeader(current)}</Text>
|
|
||||||
<Image src={images.ICON_RIGHT_MAX} className={`${styles['month-arrow']}}`} onClick={() => gotoMonth(1)} />
|
|
||||||
</View>
|
|
||||||
<View className={styles['header-actions']}>
|
|
||||||
<Image src={images.ICON_RIGHT_MAX} className={`${styles['arrow']} ${styles['left']}`} onClick={() => gotoMonth(-1)} />
|
|
||||||
<Image src={images.ICON_RIGHT_MAX} className={`${styles['arrow']}}`} onClick={() => gotoMonth(1)} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className={styles['week-row']}>
|
|
||||||
{['周日','周一','周二','周三','周四','周五','周六'].map((w) => (
|
|
||||||
<Text key={w} className={styles['week-item']}>{w}</Text>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className={styles['grid']}>
|
|
||||||
{days.map((d, idx) => {
|
|
||||||
const isSelected = !!(d && selected && d.toDateString() === new Date(selected.getFullYear(), selected.getMonth(), selected.getDate()).toDateString())
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
key={idx}
|
|
||||||
className={`${styles['cell']} ${!d ? styles['empty'] : ''} ${d && isDisabled(d) ? styles['disabled'] : ''} `}
|
|
||||||
onClick={() => handleSelectDay(d)}
|
|
||||||
>
|
|
||||||
{d ? <Text className={`${styles['cell-text']} ${isSelected ? styles['selected'] : ''}`}>{d.getDate()}</Text> : null}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CalendarCard
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import CommonPopup from '@/components/CommonPopup'
|
|
||||||
import CalendarCard from './CalendarCard'
|
|
||||||
import DateTimePicker from '@/components/DateTimePicker'
|
|
||||||
import HourMinutePicker from '@/components/HourMinutePicker'
|
|
||||||
export interface DialogCalendarCardProps {
|
|
||||||
value?: Date
|
|
||||||
minDate?: Date
|
|
||||||
maxDate?: Date
|
|
||||||
onChange?: (date: Date) => void
|
|
||||||
onNext?: (date: Date) => void
|
|
||||||
visible: boolean
|
|
||||||
onClose: () => void
|
|
||||||
title?: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
|
||||||
visible,
|
|
||||||
onClose,
|
|
||||||
title,
|
|
||||||
value,
|
|
||||||
minDate,
|
|
||||||
maxDate,
|
|
||||||
onChange,
|
|
||||||
onNext
|
|
||||||
}) => {
|
|
||||||
const [selected, setSelected] = useState<Date>(value || new Date())
|
|
||||||
const [type, setType] = useState<'year' | 'month' | 'time'>('year');
|
|
||||||
const [selectedHour, setSelectedHour] = useState(8)
|
|
||||||
const [selectedMinute, setSelectedMinute] = useState(0)
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
if (type === 'year') {
|
|
||||||
// 年份选择完成后,进入月份选择
|
|
||||||
setType('time')
|
|
||||||
} else if (type === 'month') {
|
|
||||||
// 月份选择完成后,进入时间选择
|
|
||||||
setType('year')
|
|
||||||
} else if (type === 'time') {
|
|
||||||
// 时间选择完成后,调用onNext回调
|
|
||||||
const finalDate = new Date(selected.getFullYear(), selected.getMonth(), selected.getDate(), selectedHour, selectedMinute)
|
|
||||||
console.log('finalDate', finalDate)
|
|
||||||
if (onChange) onChange(finalDate)
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (d: Date) => {
|
|
||||||
console.log('handleChange', d)
|
|
||||||
setSelected(d)
|
|
||||||
// if (onChange) onChange(d)
|
|
||||||
}
|
|
||||||
const onHeaderClick = (date: Date) => {
|
|
||||||
console.log('onHeaderClick', date)
|
|
||||||
setSelected(date)
|
|
||||||
setType('month')
|
|
||||||
}
|
|
||||||
const getConfirmText = () => {
|
|
||||||
if (type === 'time' || type === 'month') return '完成'
|
|
||||||
return '下一步'
|
|
||||||
}
|
|
||||||
const handleDateTimePickerChange = (year: number, month: number) => {
|
|
||||||
console.log('year', year)
|
|
||||||
console.log('month', month)
|
|
||||||
setSelected(new Date(year, month - 1, 1))
|
|
||||||
}
|
|
||||||
const dialogClose = () => {
|
|
||||||
if (type === 'month') {
|
|
||||||
setType('year')
|
|
||||||
} else if (type === 'time') {
|
|
||||||
setType('year')
|
|
||||||
} else {
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
setSelected(value || new Date())
|
|
||||||
if (visible) {
|
|
||||||
setType('year')
|
|
||||||
setSelectedHour(8)
|
|
||||||
setSelectedMinute(0)
|
|
||||||
}
|
|
||||||
}, [value, visible])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommonPopup
|
|
||||||
visible={visible}
|
|
||||||
onClose={dialogClose}
|
|
||||||
showHeader={!!title}
|
|
||||||
title={title}
|
|
||||||
hideFooter={false}
|
|
||||||
cancelText='取消'
|
|
||||||
confirmText={getConfirmText()}
|
|
||||||
onConfirm={handleConfirm}
|
|
||||||
position='bottom'
|
|
||||||
round
|
|
||||||
zIndex={1000}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
type === 'year' && <CalendarCard
|
|
||||||
value={selected}
|
|
||||||
minDate={minDate}
|
|
||||||
maxDate={maxDate}
|
|
||||||
onChange={handleChange}
|
|
||||||
onHeaderClick={onHeaderClick}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type === 'month' && <DateTimePicker
|
|
||||||
onChange={handleDateTimePickerChange}
|
|
||||||
defaultYear={selected.getFullYear()}
|
|
||||||
defaultMonth={selected.getMonth() + 1}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
type === 'time' && <HourMinutePicker
|
|
||||||
onChange={(hour, minute) => {
|
|
||||||
setSelectedHour(hour)
|
|
||||||
setSelectedMinute(minute)
|
|
||||||
}}
|
|
||||||
defaultHour={selectedHour}
|
|
||||||
defaultMinute={selectedMinute}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</CommonPopup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DialogCalendarCard
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
.calendar-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 12px 12px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 9px 16px 11px 16px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
.header-text {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.header-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
.month-arrow{
|
|
||||||
width: 8px
|
|
||||||
}
|
|
||||||
.arrow {
|
|
||||||
width: 10px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.arrow.left {
|
|
||||||
left: 9px;
|
|
||||||
transform: rotate(-180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.week-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
padding: 0 0 4px 0;
|
|
||||||
}
|
|
||||||
.week-item {
|
|
||||||
text-align: center;
|
|
||||||
color: rgba(60, 60, 67, 0.30);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
gap: 8px 0;
|
|
||||||
padding: 4px 0 16px;
|
|
||||||
}
|
|
||||||
.cell {
|
|
||||||
height: 44px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
.cell.empty {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.cell.disabled {
|
|
||||||
color: rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
.cell-text.selected {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 22px;
|
|
||||||
background: rgba(0,0,0,0.9);
|
|
||||||
color: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
flex: 1;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 22px;
|
|
||||||
background: rgba(0,0,0,0.06);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.btn.primary {
|
|
||||||
background: #000;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hm-placeholder {
|
|
||||||
height: 240px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { default } from './CalendarCard'
|
|
||||||
export { default as DialogCalendarCard } from './DialogCalendarCard'
|
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
height: 44px;
|
height: 44px;
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
background: #000;
|
background: #000;
|
||||||
border-radius: 12px;
|
border-radius: 12px!important;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import { View, Text, PickerView, PickerViewColumn } from '@tarojs/components'
|
|
||||||
import styles from './index.module.scss'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface DateTimePickerProps {
|
|
||||||
onChange: (year: number, month: number) => void
|
|
||||||
defaultYear?: number
|
|
||||||
defaultMonth?: number
|
|
||||||
minYear?: number
|
|
||||||
maxYear?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
||||||
|
|
||||||
onChange,
|
|
||||||
defaultYear = new Date().getFullYear(),
|
|
||||||
defaultMonth = new Date().getMonth() + 1,
|
|
||||||
minYear = 2020,
|
|
||||||
maxYear = 2030
|
|
||||||
}) => {
|
|
||||||
console.log('defaultYear', defaultYear)
|
|
||||||
console.log('defaultMonth', defaultMonth)
|
|
||||||
const [selectedYear, setSelectedYear] = useState(defaultYear)
|
|
||||||
const [selectedMonth, setSelectedMonth] = useState(defaultMonth)
|
|
||||||
|
|
||||||
// 计算当前选项在数组中的索引
|
|
||||||
const getYearIndex = (year: number) => year - minYear
|
|
||||||
const getMonthIndex = (month: number) => month - 1
|
|
||||||
|
|
||||||
// 生成多列选择器的选项数据
|
|
||||||
const pickerOptions = [
|
|
||||||
// 年份列
|
|
||||||
Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({
|
|
||||||
text: `${minYear + index}年`,
|
|
||||||
value: minYear + index
|
|
||||||
})),
|
|
||||||
// 月份列
|
|
||||||
Array.from({ length: 12 }, (_, index) => ({
|
|
||||||
text: `${index + 1}月`,
|
|
||||||
value: index + 1
|
|
||||||
}))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedYear(defaultYear)
|
|
||||||
setSelectedMonth(defaultMonth)
|
|
||||||
}, [ defaultYear, defaultMonth])
|
|
||||||
|
|
||||||
const handlePickerChange = (event: any) => {
|
|
||||||
const values = event.detail.value
|
|
||||||
if (values && values.length >= 2) {
|
|
||||||
// 根据索引获取实际值
|
|
||||||
const yearIndex = values[0]
|
|
||||||
const monthIndex = values[1]
|
|
||||||
const year = minYear + yearIndex
|
|
||||||
const month = monthIndex + 1
|
|
||||||
setSelectedYear(year)
|
|
||||||
setSelectedMonth(month)
|
|
||||||
onChange(year, month)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View className={styles['date-time-picker-overlay']} >
|
|
||||||
<View className={styles['date-time-picker-popup']} onClick={(e) => e.stopPropagation()}>
|
|
||||||
{/* 拖拽手柄 */}
|
|
||||||
<View className={styles['drag-handle']} />
|
|
||||||
|
|
||||||
{/* 时间选择器 */}
|
|
||||||
<View className={styles['picker-container']}>
|
|
||||||
{/* 多列选择器 */}
|
|
||||||
<View className={styles['picker-wrapper']}>
|
|
||||||
<PickerView
|
|
||||||
value={[getYearIndex(selectedYear), getMonthIndex(selectedMonth)]}
|
|
||||||
onChange={handlePickerChange}
|
|
||||||
className={styles['multi-column-picker']}
|
|
||||||
>
|
|
||||||
<PickerViewColumn className={styles['picker-column']}>
|
|
||||||
{pickerOptions[0].map((option, index) => (
|
|
||||||
<View key={option.value} className={styles['picker-item']}>
|
|
||||||
<Text className={styles['picker-item-text']}>{option.text}</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</PickerViewColumn>
|
|
||||||
<PickerViewColumn className={styles['picker-column']}>
|
|
||||||
{pickerOptions[1].map((option, index) => (
|
|
||||||
<View key={option.value} className={styles['picker-item']}>
|
|
||||||
<Text className={styles['picker-item-text']}>{option.text}</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</PickerViewColumn>
|
|
||||||
</PickerView>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DateTimePicker
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/* 日期选择器弹出层样式 */
|
|
||||||
.date-time-picker-popup {
|
|
||||||
.common-popup-content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-handle {
|
|
||||||
width: 32px;
|
|
||||||
height: 4px;
|
|
||||||
background: #e0e0e0;
|
|
||||||
border-radius: 2px;
|
|
||||||
margin: 12px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-container {
|
|
||||||
padding: 26px 16px 0 16px;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multi-column-picker {
|
|
||||||
width: 100%;
|
|
||||||
height: 216px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: none;
|
|
||||||
/* 自定义选择器样式 */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 选中项指示器 */
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 48px;
|
|
||||||
background: rgba(22, 24, 35, 0.05);
|
|
||||||
transform: translateY(-50%);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
color: #161823;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
&.picker-item-active {
|
|
||||||
color: rgba(22, 24, 35, 0.05);
|
|
||||||
font-weight: 600;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-column {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-item-text {
|
|
||||||
font-size: 16px;
|
|
||||||
color: inherit;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import DateTimePicker from './DateTimePicker'
|
|
||||||
export default DateTimePicker
|
|
||||||
151
src/components/Picker/CalendarDialog/DialogCalendarCard.tsx
Normal file
151
src/components/Picker/CalendarDialog/DialogCalendarCard.tsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
|
import CommonPopup from '@/components/CommonPopup'
|
||||||
|
import { View } from '@tarojs/components'
|
||||||
|
import CalendarUI, { CalendarUIRef } from '@/components/Picker/CalendarUI/CalendarUI'
|
||||||
|
import { PickerCommon, PickerCommonRef } from '@/components/Picker'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import styles from './index.module.scss'
|
||||||
|
export interface DialogCalendarCardProps {
|
||||||
|
value?: Date
|
||||||
|
onChange?: (date: Date) => void
|
||||||
|
visible: boolean
|
||||||
|
onClose: () => void
|
||||||
|
title?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const [selected, setSelected] = useState<Date>(value || new Date())
|
||||||
|
const calendarRef = useRef<CalendarUIRef>(null);
|
||||||
|
const [type, setType] = useState<'year' | 'month' | 'time'>('year');
|
||||||
|
const [selectedHour, setSelectedHour] = useState(8)
|
||||||
|
const [selectedMinute, setSelectedMinute] = useState(0)
|
||||||
|
const pickerRef = useRef<PickerCommonRef>(null);
|
||||||
|
const hourMinutePickerRef = useRef<PickerCommonRef>(null);
|
||||||
|
const [pendingJump, setPendingJump] = useState<{ year: number; month: number } | null>(null)
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (type === 'year') {
|
||||||
|
// 年份选择完成后,进入月份选择
|
||||||
|
setType('time')
|
||||||
|
} else if (type === 'month') {
|
||||||
|
// 月份选择完成后,进入时间选择
|
||||||
|
const value = pickerRef.current?.getValue()
|
||||||
|
if (value) {
|
||||||
|
const year = value[0] as number
|
||||||
|
const month = value[1] as number
|
||||||
|
setSelected(new Date(year, month - 1, 1))
|
||||||
|
setPendingJump({ year, month })
|
||||||
|
}
|
||||||
|
setType('year')
|
||||||
|
} else if (type === 'time') {
|
||||||
|
// 时间选择完成后,调用onNext回调
|
||||||
|
const value = hourMinutePickerRef.current?.getValue()
|
||||||
|
if (value) {
|
||||||
|
const hour = value[0] as number
|
||||||
|
const minute = value[1] as number
|
||||||
|
setSelectedHour(hour)
|
||||||
|
setSelectedMinute(minute)
|
||||||
|
const finalDate = new Date(dayjs(selected).format('YYYY-MM-DD') + ' ' + hour + ':' + minute)
|
||||||
|
if (onChange) onChange(finalDate)
|
||||||
|
}
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (d: Date | Date[]) => {
|
||||||
|
if (Array.isArray(d)) {
|
||||||
|
setSelected(d[0])
|
||||||
|
} else {
|
||||||
|
setSelected(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onHeaderClick = (date: Date) => {
|
||||||
|
setSelected(date)
|
||||||
|
setType('month')
|
||||||
|
}
|
||||||
|
const getConfirmText = () => {
|
||||||
|
if (type === 'time' || type === 'month') return '完成'
|
||||||
|
return '下一步'
|
||||||
|
}
|
||||||
|
const handleDateTimePickerChange = (value: (string | number)[]) => {
|
||||||
|
const year = value[0] as number
|
||||||
|
const month = value[1] as number
|
||||||
|
setSelected(new Date(year, month - 1, 1))
|
||||||
|
}
|
||||||
|
const dialogClose = () => {
|
||||||
|
if (type === 'month') {
|
||||||
|
setType('year')
|
||||||
|
} else if (type === 'time') {
|
||||||
|
setType('year')
|
||||||
|
} else {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && value) {
|
||||||
|
setSelected(value || new Date())
|
||||||
|
setSelectedHour(value ? dayjs(value).hour() : 8)
|
||||||
|
setSelectedMinute(value ? dayjs(value).minute() : 0)
|
||||||
|
}
|
||||||
|
}, [value, visible])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (type === 'year' && pendingJump && calendarRef.current) {
|
||||||
|
calendarRef.current.jumpTo(pendingJump.year, pendingJump.month)
|
||||||
|
setPendingJump(null)
|
||||||
|
}
|
||||||
|
}, [type, pendingJump])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonPopup
|
||||||
|
visible={visible}
|
||||||
|
onClose={dialogClose}
|
||||||
|
showHeader={!!title}
|
||||||
|
title={title}
|
||||||
|
hideFooter={false}
|
||||||
|
cancelText='取消'
|
||||||
|
confirmText={getConfirmText()}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
position='bottom'
|
||||||
|
round
|
||||||
|
zIndex={1000}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
type === 'year' &&
|
||||||
|
<View className={styles['calendar-container']}>
|
||||||
|
<CalendarUI
|
||||||
|
ref={calendarRef}
|
||||||
|
value={selected}
|
||||||
|
onChange={handleChange}
|
||||||
|
showQuickActions={false}
|
||||||
|
onHeaderClick={onHeaderClick}
|
||||||
|
/></View>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
type === 'month' && <PickerCommon
|
||||||
|
ref={pickerRef}
|
||||||
|
onChange={handleDateTimePickerChange}
|
||||||
|
type="month"
|
||||||
|
value={[selected.getFullYear(), selected.getMonth() + 1]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
}
|
||||||
|
{
|
||||||
|
type === 'time' && <PickerCommon
|
||||||
|
ref={hourMinutePickerRef}
|
||||||
|
type="hour"
|
||||||
|
value={[selectedHour, selectedMinute]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
}
|
||||||
|
</CommonPopup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DialogCalendarCard
|
||||||
3
src/components/Picker/CalendarDialog/index.module.scss
Normal file
3
src/components/Picker/CalendarDialog/index.module.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.calendar-container{
|
||||||
|
padding: 26px 12px 8px;
|
||||||
|
}
|
||||||
211
src/components/Picker/CalendarUI/CalendarUI.tsx
Normal file
211
src/components/Picker/CalendarUI/CalendarUI.tsx
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import React, { useState, useEffect, useRef, useImperativeHandle } from 'react'
|
||||||
|
import { CalendarCard } from '@nutui/nutui-react-taro'
|
||||||
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
|
import images from '@/config/images'
|
||||||
|
import styles from './index.module.scss'
|
||||||
|
import { getMonth, getWeekend, getWeekendOfCurrentWeek } from '@/utils/timeUtils'
|
||||||
|
import { PopupPicker } from '@/components/Picker/index'
|
||||||
|
interface NutUICalendarProps {
|
||||||
|
type?: 'single' | 'range' | 'multiple'
|
||||||
|
value?: string | Date | String[] | Date[]
|
||||||
|
defaultValue?: string | string[]
|
||||||
|
onChange?: (value: Date | Date[]) => void,
|
||||||
|
isBorder?: boolean
|
||||||
|
showQuickActions?: boolean,
|
||||||
|
onHeaderClick?: (date: Date) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarUIRef {
|
||||||
|
jumpTo: (year: number, month: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
|
||||||
|
type = 'single',
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
isBorder = false,
|
||||||
|
showQuickActions = true,
|
||||||
|
onHeaderClick
|
||||||
|
}, ref) => {
|
||||||
|
// 根据类型初始化选中值
|
||||||
|
// const getInitialValue = (): Date | Date[] => {
|
||||||
|
// console.log(value,defaultValue,'today')
|
||||||
|
|
||||||
|
// if (typeof value === 'string' && value) {
|
||||||
|
// return new Date(value)
|
||||||
|
// }
|
||||||
|
// if (Array.isArray(value) && value.length > 0) {
|
||||||
|
// return value.map(item => new Date(item))
|
||||||
|
// }
|
||||||
|
// if (typeof defaultValue === 'string' && defaultValue) {
|
||||||
|
// return new Date(defaultValue)
|
||||||
|
// }
|
||||||
|
// if (Array.isArray(defaultValue) && defaultValue.length > 0) {
|
||||||
|
// return defaultValue.map(item => new Date(item))
|
||||||
|
// }
|
||||||
|
// const today = new Date();
|
||||||
|
// if (type === 'multiple') {
|
||||||
|
// return [today]
|
||||||
|
// }
|
||||||
|
// return today
|
||||||
|
// }
|
||||||
|
const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1)
|
||||||
|
|
||||||
|
const [selectedValue, setSelectedValue] = useState<Date | Date[]>()
|
||||||
|
const [current, setCurrent] = useState<Date>(startOfMonth(new Date()))
|
||||||
|
const calendarRef = useRef<any>(null)
|
||||||
|
const [visible, setvisible] = useState(false)
|
||||||
|
console.log('current', current)
|
||||||
|
// 当外部 value 变化时更新内部状态
|
||||||
|
useEffect(() => {
|
||||||
|
if (Array.isArray(value) && value.length > 0) {
|
||||||
|
setSelectedValue(value.map(item => new Date(item)))
|
||||||
|
setCurrent(new Date(value[0]))
|
||||||
|
}
|
||||||
|
if ((typeof value === 'string' || value instanceof Date) && value) {
|
||||||
|
setSelectedValue(new Date(value))
|
||||||
|
setCurrent(new Date(value))
|
||||||
|
}
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
jumpTo: (year: number, month: number) => {
|
||||||
|
calendarRef.current?.jumpTo(year, month)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleDateChange = (newValue: any) => {
|
||||||
|
setSelectedValue(newValue)
|
||||||
|
onChange?.(newValue as any)
|
||||||
|
}
|
||||||
|
const formatHeader = (date: Date) => `${getMonth(date)}`
|
||||||
|
|
||||||
|
const handlePageChange = (data: { year: number; month: number }) => {
|
||||||
|
// 月份切换时的处理逻辑,如果需要的话
|
||||||
|
console.log('月份切换:', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const gotoMonth = (delta: number) => {
|
||||||
|
const base = current instanceof Date ? new Date(current) : new Date()
|
||||||
|
base.setMonth(base.getMonth() + delta)
|
||||||
|
const next = startOfMonth(base)
|
||||||
|
setCurrent(next)
|
||||||
|
// 同步底部 CalendarCard 的月份
|
||||||
|
try {
|
||||||
|
calendarRef.current?.jump?.(delta)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('CalendarCardRef jump 调用失败', e)
|
||||||
|
}
|
||||||
|
handlePageChange({ year: next.getFullYear(), month: next.getMonth() + 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleHeaderClick = () => {
|
||||||
|
onHeaderClick && onHeaderClick(current)
|
||||||
|
setvisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const syncMonthTo = (anchor: Date) => {
|
||||||
|
// 计算从 current 到目标 anchor 所在月份的偏移,调用 jump(delta)
|
||||||
|
const monthsDelta = (anchor.getFullYear() - current.getFullYear()) * 12 + (anchor.getMonth() - current.getMonth())
|
||||||
|
if (monthsDelta !== 0) {
|
||||||
|
gotoMonth(monthsDelta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const renderDay = (day: any) => {
|
||||||
|
const { date, month, year} = day;
|
||||||
|
const today = new Date()
|
||||||
|
if (date === today.getDate() && month === today.getMonth() + 1 && year === today.getFullYear()) {
|
||||||
|
return (
|
||||||
|
<View class="day-container">
|
||||||
|
{date}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectWeekend = () => {
|
||||||
|
const [start, end] = getWeekend()
|
||||||
|
setSelectedValue([start, end])
|
||||||
|
syncMonthTo(start)
|
||||||
|
onChange?.([start, end])
|
||||||
|
}
|
||||||
|
const selectWeek = () => {
|
||||||
|
const dayList = getWeekendOfCurrentWeek(7)
|
||||||
|
setSelectedValue(dayList)
|
||||||
|
syncMonthTo(dayList[0])
|
||||||
|
onChange?.(dayList)
|
||||||
|
}
|
||||||
|
const selectMonth = () => {
|
||||||
|
const dayList = getWeekendOfCurrentWeek(30)
|
||||||
|
setSelectedValue(dayList)
|
||||||
|
syncMonthTo(dayList[0])
|
||||||
|
onChange?.(dayList)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMonthChange = (value: any) => {
|
||||||
|
const [year, month] = value;
|
||||||
|
const newDate = new Date(year, month - 1, 1);
|
||||||
|
setCurrent(newDate);
|
||||||
|
calendarRef.current?.jumpTo(year, month)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{/* 快速操作行 */}
|
||||||
|
{
|
||||||
|
showQuickActions &&
|
||||||
|
<View className={styles['quick-actions']}>
|
||||||
|
<View className={styles['quick-action']} onClick={selectWeekend}>本周末</View>
|
||||||
|
<View className={styles['quick-action']} onClick={selectWeek}>一周内</View>
|
||||||
|
<View className={styles['quick-action']} onClick={selectMonth}>一个月</View>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
<View className={`${styles['calendar-card']} ${isBorder ? styles['border'] : ''}`}>
|
||||||
|
{/* 自定义头部显示周一到周日 */}
|
||||||
|
<View className={styles['header']}>
|
||||||
|
<View className={styles['header-left']} onClick={handleHeaderClick}>
|
||||||
|
<Text className={styles['header-text']}>{formatHeader(current as Date)}</Text>
|
||||||
|
<Image src={images.ICON_RIGHT_MAX} className={`${styles['month-arrow']}`} onClick={() => gotoMonth(1)} />
|
||||||
|
</View>
|
||||||
|
<View className={styles['header-actions']}>
|
||||||
|
<View className={styles['arrow-left-container']} onClick={() => gotoMonth(-1)}>
|
||||||
|
<Image src={images.ICON_RIGHT_MAX} className={`${styles['arrow']} ${styles['left']}`} />
|
||||||
|
</View>
|
||||||
|
<View className={styles['arrow-right-container']} onClick={() => gotoMonth(1)}>
|
||||||
|
<Image src={images.ICON_RIGHT_MAX} className={`${styles['arrow']}`} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className={styles['week-header']}>
|
||||||
|
{[ '周日', '周一', '周二', '周三', '周四', '周五', '周六'].map((day) => (
|
||||||
|
<Text key={day} className={styles['week-day']}>
|
||||||
|
{day}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* NutUI CalendarCard 组件 */}
|
||||||
|
<CalendarCard
|
||||||
|
ref={calendarRef}
|
||||||
|
type={type}
|
||||||
|
value={selectedValue}
|
||||||
|
renderDay={renderDay}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{ visible && <PopupPicker
|
||||||
|
visible={visible}
|
||||||
|
setvisible={setvisible}
|
||||||
|
value={[current.getFullYear(), current.getMonth() + 1]}
|
||||||
|
type="month"
|
||||||
|
onChange={(value) => handleMonthChange(value)}/> }
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default NutUICalendar
|
||||||
292
src/components/Picker/CalendarUI/index.module.scss
Normal file
292
src/components/Picker/CalendarUI/index.module.scss
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
.calendar-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
&.border{
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding: 12px 12px 8px;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 9px 4px 11px 4px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.header-text {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
width: 60px;
|
||||||
|
.arrow-left-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 50%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.arrow-right-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 50%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.month-arrow{
|
||||||
|
width: 8px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
width: 10px;
|
||||||
|
height: 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.arrow.left {
|
||||||
|
left: 9px;
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
padding: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
.week-item {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(60, 60, 67, 0.30);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增的周一到周日头部样式
|
||||||
|
.week-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
.week-day {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(60, 60, 67, 0.30);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 8px 0;
|
||||||
|
padding: 4px 0 16px;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.cell.empty {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.cell.disabled {
|
||||||
|
color: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.cell-text.selected {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间段选择样式
|
||||||
|
.cell-text.range-start {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-text.range-end {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-text.in-range {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(0,0,0,0.1);
|
||||||
|
color: #000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(0,0,0,0.06);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btn.primary {
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hm-placeholder {
|
||||||
|
height: 240px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalendarRange 组件样式
|
||||||
|
.calendar-range {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-select {
|
||||||
|
display: flex;
|
||||||
|
padding: 16px 12px 12px;
|
||||||
|
gap: 8px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn.active {
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn.active .quick-btn-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action {
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||||
|
background: #FFF;
|
||||||
|
display: flex;
|
||||||
|
height: 28px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 隐藏 CalendarCard 默认头部
|
||||||
|
:global {
|
||||||
|
.nut-calendarcard {
|
||||||
|
.nut-calendarcard-header {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.nut-calendarcard-content{
|
||||||
|
.nut-calendarcard-days{
|
||||||
|
&:first-child{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.nut-calendarcard-day{
|
||||||
|
margin-bottom:0px!important;
|
||||||
|
height: 44px;
|
||||||
|
width: 44px!important;
|
||||||
|
&.active{
|
||||||
|
background-color: #000!important;
|
||||||
|
color: #fff!important;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px!important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px!important;
|
||||||
|
font-size: 24px!important;
|
||||||
|
.day-container{
|
||||||
|
background-color: transparent!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.weekend{
|
||||||
|
color: rgb(0,0,0)!important;
|
||||||
|
&.active{
|
||||||
|
color: #fff!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.nut-calendarcard-day-inner{
|
||||||
|
font-size: 20px;
|
||||||
|
.day-container{
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 22px;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
86
src/components/Picker/Picker.tsx
Normal file
86
src/components/Picker/Picker.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { useState, useCallback, useEffect } from 'react'
|
||||||
|
import { Picker, ConfigProvider } from '@nutui/nutui-react-taro'
|
||||||
|
import { View } from '@tarojs/components'
|
||||||
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
|
interface PickerOption {
|
||||||
|
text: string | number
|
||||||
|
value: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PickerProps {
|
||||||
|
visible: boolean
|
||||||
|
options?: PickerOption[][]
|
||||||
|
defaultValue?: (string | number)[]
|
||||||
|
onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
|
||||||
|
onChange?: (options: PickerOption[], values: (string | number)[], columnIndex: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomPicker = ({
|
||||||
|
visible,
|
||||||
|
options = [],
|
||||||
|
defaultValue = [],
|
||||||
|
onConfirm,
|
||||||
|
onChange
|
||||||
|
}: PickerProps) => {
|
||||||
|
// 使用内部状态管理当前选中的值
|
||||||
|
const [currentValue, setCurrentValue] = useState<(string | number)[]>(defaultValue)
|
||||||
|
|
||||||
|
// 当外部 defaultValue 变化时,同步更新内部状态
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentValue(defaultValue)
|
||||||
|
}, [defaultValue])
|
||||||
|
|
||||||
|
const confirmPicker = (
|
||||||
|
options: PickerOption[],
|
||||||
|
values: (string | number)[]
|
||||||
|
) => {
|
||||||
|
let description = ''
|
||||||
|
options.forEach((option: any) => {
|
||||||
|
description += ` ${option.text}`
|
||||||
|
})
|
||||||
|
|
||||||
|
if (onConfirm) {
|
||||||
|
onConfirm(options, values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changePicker = useCallback((options: any[], values: any, columnIndex: number) => {
|
||||||
|
// 更新内部状态
|
||||||
|
setCurrentValue(values)
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
onChange(options, values, columnIndex)
|
||||||
|
}
|
||||||
|
}, [onChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View className={styles['picker-container']}>
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
nutuiPickerItemHeight: '48px',
|
||||||
|
nutuiPickerItemActiveLineBorder: 'none',
|
||||||
|
nutuiPickerItemTextColor: '#000',
|
||||||
|
nutuiPickerItemFontSize: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Picker
|
||||||
|
visible={visible}
|
||||||
|
options={options}
|
||||||
|
value={currentValue}
|
||||||
|
onChange={changePicker}
|
||||||
|
popupProps={{
|
||||||
|
overlay: false,
|
||||||
|
round: true,
|
||||||
|
zIndex: 1000,
|
||||||
|
}}
|
||||||
|
onConfirm={(list, values) => confirmPicker(list, values)}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomPicker
|
||||||
70
src/components/Picker/PickerCommon.tsx
Normal file
70
src/components/Picker/PickerCommon.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
|
import Picker from './Picker'
|
||||||
|
import { renderYearMonth, renderHourMinute } from './PickerData'
|
||||||
|
interface PickerOption {
|
||||||
|
text: string | number
|
||||||
|
value: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PickerProps {
|
||||||
|
options?: PickerOption[][]
|
||||||
|
value?: (string | number)[]
|
||||||
|
type?: 'month' | 'hour' | null
|
||||||
|
onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
|
||||||
|
onChange?: ( value: (string | number)[] ) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PickerCommonRef {
|
||||||
|
getValue: () => (string | number)[]
|
||||||
|
setValue: (v: (string | number)[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupPicker = ({
|
||||||
|
value = [],
|
||||||
|
onChange,
|
||||||
|
options = [],
|
||||||
|
type = null
|
||||||
|
}: PickerProps, ref: React.Ref<PickerCommonRef>) => {
|
||||||
|
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([])
|
||||||
|
|
||||||
|
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([])
|
||||||
|
const changePicker = (options: any[], values: any, columnIndex: number) => {
|
||||||
|
console.log('picker onChange', columnIndex, values, options)
|
||||||
|
setDefaultValue(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (type === 'month') {
|
||||||
|
setDefaultOptions(renderYearMonth())
|
||||||
|
} else if (type === 'hour') {
|
||||||
|
setDefaultOptions(renderHourMinute())
|
||||||
|
} else {
|
||||||
|
setDefaultOptions(options)
|
||||||
|
}
|
||||||
|
}, [type])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 同步初始值到内部状态,供 getValue 使用
|
||||||
|
setDefaultValue(value)
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => ({
|
||||||
|
getValue: () => (defaultValue && defaultValue.length ? defaultValue : value),
|
||||||
|
setValue: (v: (string | number)[]) => {
|
||||||
|
setDefaultValue(v)
|
||||||
|
},
|
||||||
|
}), [defaultValue, value])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Picker
|
||||||
|
visible={true}
|
||||||
|
options={defaultOptions}
|
||||||
|
defaultValue={defaultValue.length ? defaultValue : value}
|
||||||
|
onChange={changePicker}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.forwardRef<PickerCommonRef, PickerProps>(PopupPicker)
|
||||||
30
src/components/Picker/PickerData.js
Normal file
30
src/components/Picker/PickerData.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export const renderYearMonth = (minYear = 2020, maxYear = 2099) => {
|
||||||
|
return [
|
||||||
|
// 年份列
|
||||||
|
Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({
|
||||||
|
text: `${minYear + index}年`,
|
||||||
|
value: minYear + index
|
||||||
|
})),
|
||||||
|
// 月份列
|
||||||
|
Array.from({ length: 12 }, (_, index) => ({
|
||||||
|
text: `${index + 1}月`,
|
||||||
|
value: index + 1
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderHourMinute = (minHour = 0, maxHour = 23) => {
|
||||||
|
// 生成小时和分钟的选项数据
|
||||||
|
return [
|
||||||
|
// 小时列
|
||||||
|
Array.from({ length: maxHour - minHour + 1 }, (_, index) => ({
|
||||||
|
text: `${minHour + index}时`,
|
||||||
|
value: minHour + index
|
||||||
|
})),
|
||||||
|
// 分钟列 (5分钟间隔)
|
||||||
|
Array.from({ length: 12 }, (_, index) => ({
|
||||||
|
text: `${index * 5 < 10 ? '0' + index * 5 : index * 5}分`,
|
||||||
|
value: index * 5
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
}
|
||||||
90
src/components/Picker/PopupPicker.tsx
Normal file
90
src/components/Picker/PopupPicker.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
|
import CommonPopup from '@/components/CommonPopup'
|
||||||
|
import Picker from './Picker'
|
||||||
|
import { renderYearMonth, renderHourMinute } from './PickerData'
|
||||||
|
interface PickerOption {
|
||||||
|
text: string | number
|
||||||
|
value: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PickerProps {
|
||||||
|
visible: boolean
|
||||||
|
setvisible: (visible: boolean) => void
|
||||||
|
options?: PickerOption[][]
|
||||||
|
value?: (string | number)[]
|
||||||
|
type?: 'month' | 'hour' | null
|
||||||
|
onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
|
||||||
|
onChange?: ( value: (string | number)[] ) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupPicker = ({
|
||||||
|
visible,
|
||||||
|
setvisible,
|
||||||
|
value = [],
|
||||||
|
onConfirm,
|
||||||
|
onChange,
|
||||||
|
options = [],
|
||||||
|
type = null
|
||||||
|
}: PickerProps) => {
|
||||||
|
|
||||||
|
const [defaultValue, setDefaultValue] = useState<(string | number)[]>([])
|
||||||
|
const [defaultOptions, setDefaultOptions] = useState<PickerOption[][]>([])
|
||||||
|
const changePicker = (options: any[], values: any, columnIndex: number) => {
|
||||||
|
if (onChange) {
|
||||||
|
console.log('picker onChange', columnIndex, values, options)
|
||||||
|
|
||||||
|
setDefaultValue(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
console.log(defaultValue,'defaultValue');
|
||||||
|
onChange(defaultValue)
|
||||||
|
setvisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogClose = () => {
|
||||||
|
setvisible(false)
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if (type === 'month') {
|
||||||
|
setDefaultOptions(renderYearMonth())
|
||||||
|
} else if (type === 'hour') {
|
||||||
|
setDefaultOptions(renderHourMinute())
|
||||||
|
} else {
|
||||||
|
setDefaultOptions(options)
|
||||||
|
}
|
||||||
|
}, [type])
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (value.length > 0 && defaultOptions.length > 0) {
|
||||||
|
// setDefaultValue([...value])
|
||||||
|
// }
|
||||||
|
// }, [value, defaultOptions])
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CommonPopup
|
||||||
|
visible={visible}
|
||||||
|
onClose={dialogClose}
|
||||||
|
showHeader={false}
|
||||||
|
title={null}
|
||||||
|
hideFooter={false}
|
||||||
|
cancelText='取消'
|
||||||
|
confirmText='完成'
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
position='bottom'
|
||||||
|
round
|
||||||
|
zIndex={1000}
|
||||||
|
>
|
||||||
|
<Picker
|
||||||
|
visible={visible}
|
||||||
|
options={defaultOptions}
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={changePicker}
|
||||||
|
/>
|
||||||
|
</CommonPopup>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PopupPicker
|
||||||
25
src/components/Picker/index.module.scss
Normal file
25
src/components/Picker/index.module.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
.picker-container {
|
||||||
|
:global{
|
||||||
|
.nut-popup-round{
|
||||||
|
position: relative!important;
|
||||||
|
.nut-picker-control {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
.nut-picker{
|
||||||
|
&::after{
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 16px;
|
||||||
|
right: 16px!important;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(22, 24, 35, 0.05);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/components/Picker/index.ts
Normal file
6
src/components/Picker/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export { default as CustomPicker } from './Picker'
|
||||||
|
export { default as PopupPicker } from './PopupPicker'
|
||||||
|
export { default as PickerCommon } from './PickerCommon'
|
||||||
|
export type { PickerCommonRef } from './PickerCommon'
|
||||||
|
export { default as CalendarUI } from './CalendarUI/CalendarUI'
|
||||||
|
export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard'
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { View, Text, } from '@tarojs/components'
|
import { View, Text, } from '@tarojs/components'
|
||||||
import { getDate, getTime, getDateStr, getEndTime } from '@/utils/timeUtils'
|
import { getDate, getTime, getDateStr, getEndTime } from '@/utils/timeUtils'
|
||||||
import DialogCalendarCard from '@/components/CalendarCard/DialogCalendarCard'
|
import { DialogCalendarCard } from '@/components/index'
|
||||||
import './TimeSelector.scss'
|
import './TimeSelector.scss'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
export interface TimeRange {
|
export interface TimeRange {
|
||||||
start_time: string
|
start_time: string
|
||||||
@@ -23,12 +24,38 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// 格式化日期显示
|
// 格式化日期显示
|
||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
|
const [currentTimeValue, setCurrentTimeValue] = useState<Date>(new Date())
|
||||||
|
const [currentTimeType, setCurrentTimeType] = useState<'start' | 'end'>('start')
|
||||||
|
const [showEndTime, setShowEndTime] = useState(false)
|
||||||
const handleConfirm = (date: Date) => {
|
const handleConfirm = (date: Date) => {
|
||||||
console.log('选择的日期:', date)
|
console.log('选择的日期:', date)
|
||||||
const start_time = getDateStr(date)
|
const start_time = currentTimeType === 'start' ? getDateStr(date) : value.start_time;
|
||||||
const end_time = getEndTime(start_time)
|
const isLater = dayjs(value.start_time).isAfter(dayjs(value.end_time));
|
||||||
|
if (isLater) {
|
||||||
|
if (onChange) onChange({start_time, end_time: getEndTime(start_time)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const initEndTime = value.end_time ? value.end_time : getEndTime(start_time)
|
||||||
|
const end_time = currentTimeType === 'end' ? getDateStr(date) : initEndTime;
|
||||||
if (onChange) onChange({start_time, end_time})
|
if (onChange) onChange({start_time, end_time})
|
||||||
}
|
}
|
||||||
|
const openPicker = (type: 'start' | 'end') => {
|
||||||
|
setCurrentTimeValue(type === 'start' ? new Date(value.start_time) : new Date(value.end_time))
|
||||||
|
setCurrentTimeType(type)
|
||||||
|
setVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value.start_time && value.end_time) {
|
||||||
|
const start_time = dayjs(value.start_time).format('YYYY-MM-DD')
|
||||||
|
const end_time = dayjs(value.end_time).format('YYYY-MM-DD')
|
||||||
|
if (start_time === end_time) {
|
||||||
|
setShowEndTime(false)
|
||||||
|
} else {
|
||||||
|
setShowEndTime(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [value])
|
||||||
return (
|
return (
|
||||||
<View className='time-selector'>
|
<View className='time-selector'>
|
||||||
<View className='time-section'>
|
<View className='time-section'>
|
||||||
@@ -37,7 +64,7 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
|
|||||||
<View className='time-label'>
|
<View className='time-label'>
|
||||||
<View className='dot'></View>
|
<View className='dot'></View>
|
||||||
</View>
|
</View>
|
||||||
<View className='time-content' onClick={() => setVisible(true)}>
|
<View className='time-content' onClick={() => openPicker('start')}>
|
||||||
<Text className='time-label'>开始时间</Text>
|
<Text className='time-label'>开始时间</Text>
|
||||||
<view className='time-text-wrapper'>
|
<view className='time-text-wrapper'>
|
||||||
<Text className='time-text'>{getDate(value.start_time)}</Text>
|
<Text className='time-text'>{getDate(value.start_time)}</Text>
|
||||||
@@ -51,9 +78,10 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
|
|||||||
<View className='time-label'>
|
<View className='time-label'>
|
||||||
<View className='dot hollow'></View>
|
<View className='dot hollow'></View>
|
||||||
</View>
|
</View>
|
||||||
<View className='time-content'>
|
<View className='time-content' onClick={() => openPicker('end')}>
|
||||||
<Text className='time-label'>结束时间</Text>
|
<Text className='time-label'>结束时间</Text>
|
||||||
<view className='time-text-wrapper'>
|
<view className='time-text-wrapper'>
|
||||||
|
{showEndTime && (<Text className='time-text'>{getDate(value.end_time)}</Text>)}
|
||||||
<Text className='time-text time-am'>{getTime(value.end_time)}</Text>
|
<Text className='time-text time-am'>{getTime(value.end_time)}</Text>
|
||||||
</view>
|
</view>
|
||||||
</View>
|
</View>
|
||||||
@@ -61,6 +89,7 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
<DialogCalendarCard
|
<DialogCalendarCard
|
||||||
visible={visible}
|
visible={visible}
|
||||||
|
value={currentTimeValue}
|
||||||
onChange={handleConfirm}
|
onChange={handleConfirm}
|
||||||
onClose={() => setVisible(false)}
|
onClose={() => setVisible(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import NumberInterval from './NumberInterval'
|
|||||||
import TimeSelector from './TimeSelector'
|
import TimeSelector from './TimeSelector'
|
||||||
import TitleTextarea from './TitleTextarea'
|
import TitleTextarea from './TitleTextarea'
|
||||||
import CommonPopup from './CommonPopup'
|
import CommonPopup from './CommonPopup'
|
||||||
import DateTimePicker from './DateTimePicker/DateTimePicker'
|
|
||||||
import TimePicker from './TimePicker/TimePicker'
|
import TimePicker from './TimePicker/TimePicker'
|
||||||
import CalendarCard, { DialogCalendarCard } from './CalendarCard'
|
import { CalendarUI, DialogCalendarCard } from './Picker'
|
||||||
import CommonDialog from './CommonDialog'
|
import CommonDialog from './CommonDialog'
|
||||||
import PublishMenu from './PublishMenu/PublishMenu'
|
import PublishMenu from './PublishMenu/PublishMenu'
|
||||||
import UploadCover from './UploadCover'
|
import UploadCover from './UploadCover'
|
||||||
|
import { CustomPicker, PopupPicker } from './Picker'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ActivityTypeSwitch,
|
ActivityTypeSwitch,
|
||||||
@@ -25,12 +25,12 @@ import UploadCover from './UploadCover'
|
|||||||
TimeSelector,
|
TimeSelector,
|
||||||
TitleTextarea,
|
TitleTextarea,
|
||||||
CommonPopup,
|
CommonPopup,
|
||||||
DateTimePicker,
|
|
||||||
TimePicker,
|
TimePicker,
|
||||||
CalendarCard,
|
|
||||||
DialogCalendarCard,
|
DialogCalendarCard,
|
||||||
|
CalendarUI,
|
||||||
CommonDialog,
|
CommonDialog,
|
||||||
PublishMenu,
|
PublishMenu,
|
||||||
UploadCover
|
UploadCover,
|
||||||
|
CustomPicker,
|
||||||
|
PopupPicker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
import { Popup } from "@nutui/nutui-react-taro";
|
import { Popup } from "@nutui/nutui-react-taro";
|
||||||
import Range from "../../components/Range";
|
import Range from "../../components/Range";
|
||||||
import Bubble from "../../components/Bubble";
|
import Bubble from "../../components/Bubble";
|
||||||
@@ -6,14 +7,15 @@ import TitleComponent from "@/components/Title";
|
|||||||
import { Button } from "@nutui/nutui-react-taro";
|
import { Button } from "@nutui/nutui-react-taro";
|
||||||
import { Image } from "@tarojs/components";
|
import { Image } from "@tarojs/components";
|
||||||
import img from "../../config/images";
|
import img from "../../config/images";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import { useListStore } from "src/store/listStore";
|
import { useListStore } from "src/store/listStore";
|
||||||
import { FilterPopupProps } from "../../../types/list/types";
|
import { FilterPopupProps } from "../../../types/list/types";
|
||||||
|
import { CalendarUI } from "@/components";
|
||||||
// 场地
|
// 场地
|
||||||
import CourtType from "@/components/CourtType";
|
import CourtType from "@/components/CourtType";
|
||||||
// 玩法
|
// 玩法
|
||||||
import GamePlayType from "@/components/GamePlayType";
|
import GamePlayType from "@/components/GamePlayType";
|
||||||
import { useDictionaryActions } from "@/store/dictionaryStore";
|
import { useDictionaryActions } from "@/store/dictionaryStore";
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
const FilterPopup = (props: FilterPopupProps) => {
|
const FilterPopup = (props: FilterPopupProps) => {
|
||||||
const {
|
const {
|
||||||
@@ -32,6 +34,14 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
const { getDictionaryValue } = useDictionaryActions() || {};
|
const { getDictionaryValue } = useDictionaryActions() || {};
|
||||||
const { timeBubbleData } = store;
|
const { timeBubbleData } = store;
|
||||||
|
|
||||||
|
const [selectedDates, setSelectedDates] = useState<String[]>([])
|
||||||
|
|
||||||
|
const handleDateChange = (dates: Date[]) => {
|
||||||
|
const dateArray = dates.map(date => dayjs(date).format('YYYY-MM-DD'))
|
||||||
|
setSelectedDates(dateArray)
|
||||||
|
console.log('选中的日期:', dateArray)
|
||||||
|
}
|
||||||
|
|
||||||
const handleOptions = (dictionaryValue: []) => {
|
const handleOptions = (dictionaryValue: []) => {
|
||||||
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
|
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
|
||||||
};
|
};
|
||||||
@@ -68,6 +78,12 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
>
|
>
|
||||||
<div className={styles.filterPopupWrapper}>
|
<div className={styles.filterPopupWrapper}>
|
||||||
{/* 时间气泡选项 */}
|
{/* 时间气泡选项 */}
|
||||||
|
<CalendarUI
|
||||||
|
type="multiple"
|
||||||
|
isBorder={true}
|
||||||
|
value={selectedDates}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
/>
|
||||||
<Bubble
|
<Bubble
|
||||||
options={timeBubbleData}
|
options={timeBubbleData}
|
||||||
value={filterOptions?.time}
|
value={filterOptions?.time}
|
||||||
|
|||||||
@@ -29,6 +29,40 @@ export const getDate = (date: string): string => {
|
|||||||
return dayjs(date).format('YYYY年MM月DD日')
|
return dayjs(date).format('YYYY年MM月DD日')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getDay = (date?: string | Date): string => {
|
||||||
|
if (!date) {
|
||||||
|
return dayjs().format('YYYY-MM-DD')
|
||||||
|
}
|
||||||
|
return dayjs(date).format('YYYY-MM-DD')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMonth = (date?: string | Date): string => {
|
||||||
|
if (!date) {
|
||||||
|
return dayjs().format('MM月 YYYY')
|
||||||
|
}
|
||||||
|
return dayjs(date).format('MM月 YYYY')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWeekend = (date?: string | Date): [Date, Date] => {
|
||||||
|
const today = dayjs(date);
|
||||||
|
const currentDayOfWeek = today.day();
|
||||||
|
console.log('currentDayOfWeek', currentDayOfWeek)
|
||||||
|
const saturdayOffset = 6 - currentDayOfWeek
|
||||||
|
const sundayOffset = 7 - currentDayOfWeek
|
||||||
|
const sat = today.add(saturdayOffset, 'day')
|
||||||
|
const sun = today.add(sundayOffset, 'day')
|
||||||
|
return [sat.toDate(), sun.toDate()]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWeekendOfCurrentWeek = (days = 7): Date[] => {
|
||||||
|
const dayList: Date[] = [];
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const day = dayjs().add(i, 'day').toDate()
|
||||||
|
dayList.push(day)
|
||||||
|
}
|
||||||
|
return dayList
|
||||||
|
}
|
||||||
|
|
||||||
export const getTime = (time: string): string => {
|
export const getTime = (time: string): string => {
|
||||||
const timeObj = dayjs(time)
|
const timeObj = dayjs(time)
|
||||||
const hour = timeObj.hour()
|
const hour = timeObj.hour()
|
||||||
|
|||||||
Reference in New Issue
Block a user