修改发布日历
This commit is contained in:
117
src/components/CalendarCard/CalendarCard.tsx
Normal file
117
src/components/CalendarCard/CalendarCard.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
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
|
||||
130
src/components/CalendarCard/DialogCalendarCard.tsx
Normal file
130
src/components/CalendarCard/DialogCalendarCard.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
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
|
||||
105
src/components/CalendarCard/index.module.scss
Normal file
105
src/components/CalendarCard/index.module.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
2
src/components/CalendarCard/index.ts
Normal file
2
src/components/CalendarCard/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './CalendarCard'
|
||||
export { default as DialogCalendarCard } from './DialogCalendarCard'
|
||||
Reference in New Issue
Block a user