diff --git a/src/components/CalendarCard/CalendarCard.tsx b/src/components/CalendarCard/CalendarCard.tsx deleted file mode 100644 index eb1e23c..0000000 --- a/src/components/CalendarCard/CalendarCard.tsx +++ /dev/null @@ -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 = ({ - value, - minDate, - maxDate, - onChange, - onHeaderClick -}) => { - const today = new Date() - const [current, setCurrent] = useState(value || startOfMonth(today)) - const [selected, setSelected] = useState(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 ( - - - - {formatHeader(current)} - gotoMonth(1)} /> - - - gotoMonth(-1)} /> - gotoMonth(1)} /> - - - - - {['周日','周一','周二','周三','周四','周五','周六'].map((w) => ( - {w} - ))} - - - - {days.map((d, idx) => { - const isSelected = !!(d && selected && d.toDateString() === new Date(selected.getFullYear(), selected.getMonth(), selected.getDate()).toDateString()) - return ( - handleSelectDay(d)} - > - {d ? {d.getDate()} : null} - - ) - })} - - - - - - - ) -} - -export default CalendarCard diff --git a/src/components/CalendarCard/DialogCalendarCard.tsx b/src/components/CalendarCard/DialogCalendarCard.tsx deleted file mode 100644 index 92bfec4..0000000 --- a/src/components/CalendarCard/DialogCalendarCard.tsx +++ /dev/null @@ -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 = ({ - visible, - onClose, - title, - value, - minDate, - maxDate, - onChange, - onNext -}) => { - const [selected, setSelected] = useState(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 ( - - { - type === 'year' && - } - { - type === 'month' && - } - { - type === 'time' && { - setSelectedHour(hour) - setSelectedMinute(minute) - }} - defaultHour={selectedHour} - defaultMinute={selectedMinute} - /> - } - - ) -} - -export default DialogCalendarCard diff --git a/src/components/CalendarCard/index.ts b/src/components/CalendarCard/index.ts deleted file mode 100644 index 15ceb7e..0000000 --- a/src/components/CalendarCard/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './CalendarCard' -export { default as DialogCalendarCard } from './DialogCalendarCard' diff --git a/src/components/CommonPopup/index.module.scss b/src/components/CommonPopup/index.module.scss index 1fb1e10..7210f10 100644 --- a/src/components/CommonPopup/index.module.scss +++ b/src/components/CommonPopup/index.module.scss @@ -75,7 +75,7 @@ height: 44px; border: 0.5px solid rgba(0, 0, 0, 0.06); background: #000; - border-radius: 12px; + border-radius: 12px!important; padding: 4px 10px; } } \ No newline at end of file diff --git a/src/components/DateTimePicker/DateTimePicker.tsx b/src/components/DateTimePicker/DateTimePicker.tsx deleted file mode 100644 index c5714e9..0000000 --- a/src/components/DateTimePicker/DateTimePicker.tsx +++ /dev/null @@ -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 = ({ - - 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 ( - - e.stopPropagation()}> - {/* 拖拽手柄 */} - - - {/* 时间选择器 */} - - {/* 多列选择器 */} - - - - {pickerOptions[0].map((option, index) => ( - - {option.text} - - ))} - - - {pickerOptions[1].map((option, index) => ( - - {option.text} - - ))} - - - - - - - ) -} - -export default DateTimePicker diff --git a/src/components/DateTimePicker/index.module.scss b/src/components/DateTimePicker/index.module.scss deleted file mode 100644 index 67c6587..0000000 --- a/src/components/DateTimePicker/index.module.scss +++ /dev/null @@ -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; -} - diff --git a/src/components/DateTimePicker/index.ts b/src/components/DateTimePicker/index.ts deleted file mode 100644 index 4817df4..0000000 --- a/src/components/DateTimePicker/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import DateTimePicker from './DateTimePicker' -export default DateTimePicker diff --git a/src/components/FilterPopup/index.tsx b/src/components/FilterPopup/index.tsx index a83a5bb..1aff3d9 100644 --- a/src/components/FilterPopup/index.tsx +++ b/src/components/FilterPopup/index.tsx @@ -1,3 +1,4 @@ +import { useMemo, useState } from "react"; import { Popup } from "@nutui/nutui-react-taro"; import Range from "../../components/Range"; import Bubble from "../../components/Bubble"; @@ -5,15 +6,14 @@ import styles from "./index.module.scss"; import { Button } from "@nutui/nutui-react-taro"; import { useListStore } from "src/store/listStore"; import { BubbleOption, FilterPopupProps } from "../../../types/list/types"; -import CalendarCard from "@/components/CalendarCard/index"; import dateRangeUtils from '@/utils/dateRange' - +import dayjs from "dayjs"; +import { CalendarUI } from "@/components"; // 场地 import CourtType from "@/components/CourtType"; // 玩法 import GamePlayType from "@/components/GamePlayType"; import { useDictionaryActions } from "@/store/dictionaryStore"; -import { useMemo } from "react"; import { View } from "@tarojs/components"; @@ -44,6 +44,14 @@ const FilterPopup = (props: FilterPopupProps) => { * @param dictionaryValue 字典选项 * @returns 选项列表 */ + const [selectedDates, setSelectedDates] = useState([]) + + const handleDateChange = (dates: Date[]) => { + const dateArray = dates.map(date => dayjs(date).format('YYYY-MM-DD')) + setSelectedDates(dateArray) + console.log('选中的日期:', dateArray) + } + const handleOptions = (dictionaryValue: []) => { return dictionaryValue?.map((item) => ({ label: item, value: item })) || []; }; @@ -131,13 +139,15 @@ const FilterPopup = (props: FilterPopupProps) => { name="dateRangeQuick" /> - + {/* 时间气泡选项 */} + void + visible: boolean + onClose: () => void + title?: React.ReactNode +} + +const DialogCalendarCard: React.FC = ({ + visible, + onClose, + title, + value, + onChange, +}) => { + const [selected, setSelected] = useState(value || new Date()) + const calendarRef = useRef(null); + const [type, setType] = useState<'year' | 'month' | 'time'>('year'); + const [selectedHour, setSelectedHour] = useState(8) + const [selectedMinute, setSelectedMinute] = useState(0) + const pickerRef = useRef(null); + const hourMinutePickerRef = useRef(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 ( + + { + type === 'year' && + + + } + { + type === 'month' && + + } + { + type === 'time' && + + } + + ) +} + +export default DialogCalendarCard diff --git a/src/components/Picker/CalendarDialog/index.module.scss b/src/components/Picker/CalendarDialog/index.module.scss new file mode 100644 index 0000000..95e8153 --- /dev/null +++ b/src/components/Picker/CalendarDialog/index.module.scss @@ -0,0 +1,3 @@ +.calendar-container{ + padding: 26px 12px 8px; +} \ No newline at end of file diff --git a/src/components/Picker/CalendarUI/CalendarUI.tsx b/src/components/Picker/CalendarUI/CalendarUI.tsx new file mode 100644 index 0000000..16fa745 --- /dev/null +++ b/src/components/Picker/CalendarUI/CalendarUI.tsx @@ -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(({ + 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() + const [current, setCurrent] = useState(startOfMonth(new Date())) + const calendarRef = useRef(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 ( + + {date} + + ) + } + 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 ( + + {/* 快速操作行 */} + { + showQuickActions && + + 本周末 + 一周内 + 一个月 + + } + + {/* 自定义头部显示周一到周日 */} + + + {formatHeader(current as Date)} + gotoMonth(1)} /> + + + gotoMonth(-1)}> + + + gotoMonth(1)}> + + + + + + {[ '周日', '周一', '周二', '周三', '周四', '周五', '周六'].map((day) => ( + + {day} + + ))} + + + {/* NutUI CalendarCard 组件 */} + + + { visible && handleMonthChange(value)}/> } + + ) +}) + +export default NutUICalendar \ No newline at end of file diff --git a/src/components/Picker/CalendarUI/index.module.scss b/src/components/Picker/CalendarUI/index.module.scss new file mode 100644 index 0000000..ce79dc7 --- /dev/null +++ b/src/components/Picker/CalendarUI/index.module.scss @@ -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; + } + } +} + diff --git a/src/components/Picker/Picker.tsx b/src/components/Picker/Picker.tsx new file mode 100644 index 0000000..e0a1f31 --- /dev/null +++ b/src/components/Picker/Picker.tsx @@ -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 ( + <> + + + confirmPicker(list, values)} + /> + + + + ) +} + +export default CustomPicker \ No newline at end of file diff --git a/src/components/Picker/PickerCommon.tsx b/src/components/Picker/PickerCommon.tsx new file mode 100644 index 0000000..4581e4b --- /dev/null +++ b/src/components/Picker/PickerCommon.tsx @@ -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) => { + const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]) + + const [defaultOptions, setDefaultOptions] = useState([]) + 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 ( + <> + + + ) +} + +export default React.forwardRef(PopupPicker) diff --git a/src/components/Picker/PickerData.js b/src/components/Picker/PickerData.js new file mode 100644 index 0000000..c145e8d --- /dev/null +++ b/src/components/Picker/PickerData.js @@ -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 + })) + ] +} \ No newline at end of file diff --git a/src/components/Picker/PopupPicker.tsx b/src/components/Picker/PopupPicker.tsx new file mode 100644 index 0000000..2f2292d --- /dev/null +++ b/src/components/Picker/PopupPicker.tsx @@ -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([]) + 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 ( + <> + + + + + ) +} + +export default PopupPicker diff --git a/src/components/Picker/index.module.scss b/src/components/Picker/index.module.scss new file mode 100644 index 0000000..841cdee --- /dev/null +++ b/src/components/Picker/index.module.scss @@ -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; + } + } + } + } +} diff --git a/src/components/Picker/index.ts b/src/components/Picker/index.ts new file mode 100644 index 0000000..a8915a1 --- /dev/null +++ b/src/components/Picker/index.ts @@ -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' \ No newline at end of file diff --git a/src/components/TimeSelector/TimeSelector.tsx b/src/components/TimeSelector/TimeSelector.tsx index 1390d66..ef4e934 100644 --- a/src/components/TimeSelector/TimeSelector.tsx +++ b/src/components/TimeSelector/TimeSelector.tsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import { View, Text, } from '@tarojs/components' import { getDate, getTime, getDateStr, getEndTime } from '@/utils/timeUtils' -import DialogCalendarCard from '@/components/CalendarCard/DialogCalendarCard' +import { DialogCalendarCard } from '@/components/index' import './TimeSelector.scss' +import dayjs from 'dayjs' export interface TimeRange { start_time: string @@ -23,12 +24,38 @@ const TimeSelector: React.FC = ({ }) => { // 格式化日期显示 const [visible, setVisible] = useState(false) + const [currentTimeValue, setCurrentTimeValue] = useState(new Date()) + const [currentTimeType, setCurrentTimeType] = useState<'start' | 'end'>('start') + const [showEndTime, setShowEndTime] = useState(false) const handleConfirm = (date: Date) => { console.log('选择的日期:', date) - const start_time = getDateStr(date) - const end_time = getEndTime(start_time) + const start_time = currentTimeType === 'start' ? getDateStr(date) : value.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}) } + 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 ( @@ -37,7 +64,7 @@ const TimeSelector: React.FC = ({ - setVisible(true)}> + openPicker('start')}> 开始时间 {getDate(value.start_time)} @@ -51,9 +78,10 @@ const TimeSelector: React.FC = ({ - + openPicker('end')}> 结束时间 + {showEndTime && ({getDate(value.end_time)})} {getTime(value.end_time)} @@ -61,6 +89,7 @@ const TimeSelector: React.FC = ({ setVisible(false)} /> diff --git a/src/components/index.ts b/src/components/index.ts index 141a971..abf5a73 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,14 +8,14 @@ import NumberInterval from './NumberInterval' import TimeSelector from './TimeSelector' import TitleTextarea from './TitleTextarea' import CommonPopup from './CommonPopup' -import DateTimePicker from './DateTimePicker/DateTimePicker' import TimePicker from './TimePicker/TimePicker' -import CalendarCard, { DialogCalendarCard } from './CalendarCard' +import { CalendarUI, DialogCalendarCard } from './Picker' import CommonDialog from './CommonDialog' import PublishMenu from './PublishMenu/PublishMenu' import UploadCover from './UploadCover' import EditModal from './EditModal/index' import withAuth from './Auth' +import { CustomPicker, PopupPicker } from './Picker' export { ActivityTypeSwitch, @@ -27,14 +27,14 @@ import withAuth from './Auth' TimeSelector, TitleTextarea, CommonPopup, - DateTimePicker, TimePicker, - CalendarCard, DialogCalendarCard, + CalendarUI, CommonDialog, PublishMenu, UploadCover, EditModal, withAuth, + CustomPicker, + PopupPicker } - diff --git a/src/utils/timeUtils.ts b/src/utils/timeUtils.ts index 74d65d0..86438f8 100644 --- a/src/utils/timeUtils.ts +++ b/src/utils/timeUtils.ts @@ -29,6 +29,40 @@ export const getDate = (date: string): string => { 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 => { const timeObj = dayjs(time) const hour = timeObj.hour() diff --git a/types/publishBall.ts b/types/publishBall.ts index 198215c..a0bc55c 100644 --- a/types/publishBall.ts +++ b/types/publishBall.ts @@ -1,7 +1,7 @@ export interface PublishBallFormData { title: string // 球局标题 - image_list: string[] // 球局封面 + image_list: {url: string, id: string}[] // 球局封面 timeRange: { start_time: string, end_time: string