From aef84e76cbc32e996519214ce9631bd03b39e268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E9=87=8E?= Date: Sat, 13 Sep 2025 10:36:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TimePicker/README.md | 77 ------ src/components/TimePicker/TimePicker.tsx | 233 ------------------ src/components/TimePicker/demo.module.scss | 81 ------ src/components/TimePicker/demo.tsx | 55 ----- src/components/TimePicker/index.module.scss | 187 -------------- src/components/TimePicker/index.ts | 2 - .../TimePicker/layout-test.module.scss | 59 ----- src/components/TimePicker/layout-test.tsx | 51 ---- src/components/TimePicker/test.module.scss | 36 --- src/components/TimePicker/test.tsx | 46 ---- src/components/index.ts | 2 - 11 files changed, 829 deletions(-) delete mode 100644 src/components/TimePicker/README.md delete mode 100644 src/components/TimePicker/TimePicker.tsx delete mode 100644 src/components/TimePicker/demo.module.scss delete mode 100644 src/components/TimePicker/demo.tsx delete mode 100644 src/components/TimePicker/index.module.scss delete mode 100644 src/components/TimePicker/index.ts delete mode 100644 src/components/TimePicker/layout-test.module.scss delete mode 100644 src/components/TimePicker/layout-test.tsx delete mode 100644 src/components/TimePicker/test.module.scss delete mode 100644 src/components/TimePicker/test.tsx diff --git a/src/components/TimePicker/README.md b/src/components/TimePicker/README.md deleted file mode 100644 index bf70b24..0000000 --- a/src/components/TimePicker/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# TimePicker 时间选择器组件 - -## 功能特性 - -- 使用自定义样式重写PickerViewColumn功能 -- 完全还原原生PickerView的样式和动画效果 -- 支持年份和月份选择 -- 平滑的滚动动画和切换效果 -- 响应式设计,支持触摸滚动 -- 渐变遮罩效果增强视觉层次 - -## 技术实现 - -### 核心特性 -- 使用ScrollView替代PickerViewColumn -- 自定义滚动逻辑实现选项对齐 -- CSS动画和过渡效果还原原生体验 -- 智能滚动位置计算和自动对齐 - -### 样式还原 -- 选中项指示器(高亮背景) -- 渐变遮罩效果(顶部和底部) -- 平滑的过渡动画 -- 精确的尺寸和间距 - -## 使用方法 - -```tsx -import { TimePicker } from '@/components/TimePicker' - -const [visible, setVisible] = useState(false) - - setVisible(false)} - onConfirm={(year, month) => { - console.log('选择的时间:', year, month) - setVisible(false) - }} - defaultYear={2024} - defaultMonth={6} - minYear={2020} - maxYear={2030} -/> -``` - -## Props - -| 属性 | 类型 | 默认值 | 说明 | -|------|------|--------|------| -| visible | boolean | - | 是否显示选择器 | -| visible | boolean | - | 是否显示选择器 | -| onClose | () => void | - | 关闭回调 | -| onConfirm | (year: number, month: number) => void | - | 确认选择回调 | -| defaultYear | number | 当前年份 | 默认选中的年份 | -| defaultMonth | number | 当前月份 | 默认选中的月份 | -| minYear | number | 2020 | 最小年份 | -| maxYear | number | 2030 | 最大年份 | - -## 样式定制 - -组件使用CSS Modules,可以通过修改`index.module.scss`文件来自定义样式: - -- `.time-picker-popup`: 弹出层容器 -- `.picker-container`: 选择器容器 -- `.custom-picker`: 自定义选择器 -- `.picker-indicator`: 选中项指示器 -- `.picker-column`: 选择列 -- `.picker-item`: 选择项 -- `.picker-item-active`: 激活状态的选择项 - -## 测试 - -运行测试页面: -```tsx -import TimePickerTest from '@/components/TimePicker/test' -``` \ No newline at end of file diff --git a/src/components/TimePicker/TimePicker.tsx b/src/components/TimePicker/TimePicker.tsx deleted file mode 100644 index b18ff41..0000000 --- a/src/components/TimePicker/TimePicker.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react' -import { View, Text, ScrollView } from '@tarojs/components' -import { CommonPopup } from '../index' -import styles from './index.module.scss' - -export interface TimePickerProps { - visible: boolean - onClose: () => void - onConfirm: (year: number, month: number) => void - defaultYear?: number - defaultMonth?: number - minYear?: number - maxYear?: number -} - -const TimePicker: React.FC = ({ - visible, - onClose, - onConfirm, - defaultYear = new Date().getFullYear(), - defaultMonth = new Date().getMonth() + 1, - minYear = 2020, - maxYear = 2030 -}) => { - const [selectedYear, setSelectedYear] = useState(defaultYear) - const [selectedMonth, setSelectedMonth] = useState(defaultMonth) - const [yearScrollTop, setYearScrollTop] = useState(0) - const [monthScrollTop, setMonthScrollTop] = useState(0) - - const yearScrollRef = useRef(null) - const monthScrollRef = useRef(null) - - // 计算当前选项在数组中的索引 - const getYearIndex = (year: number) => year - minYear - const getMonthIndex = (month: number) => month - 1 - - // 生成选择器的选项数据 - const yearOptions = Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({ - text: `${minYear + index}年`, - value: minYear + index - })) - - const monthOptions = Array.from({ length: 12 }, (_, index) => ({ - text: `${index + 1}月`, - value: index + 1 - })) - - // 计算滚动位置 - 确保每次只显示一个选项 - const calculateScrollTop = (index: number) => { - const itemHeight = 48 // 每个选项的高度 - const containerHeight = 216 // 容器高度 - const centerOffset = (containerHeight - itemHeight) / 2 - return index * itemHeight - centerOffset - } - - // 获取当前可见的选项数量 - const getVisibleItemCount = () => { - const containerHeight = 216 - const itemHeight = 48 - return Math.floor(containerHeight / itemHeight) - } - - useEffect(() => { - if (visible) { - setSelectedYear(defaultYear) - setSelectedMonth(defaultMonth) - - // 设置初始滚动位置 - const yearScrollTop = calculateScrollTop(getYearIndex(defaultYear)) - const monthScrollTop = calculateScrollTop(getMonthIndex(defaultMonth)) - setYearScrollTop(yearScrollTop) - setMonthScrollTop(monthScrollTop) - } - }, [visible, defaultYear, defaultMonth]) - - // 处理年份滚动 - const handleYearScroll = (event: any) => { - const scrollTop = event.detail.scrollTop - const itemHeight = 48 - const containerHeight = 216 - const centerOffset = (containerHeight - itemHeight) / 2 - - // 计算当前选中的年份索引 - const currentIndex = Math.round((scrollTop + centerOffset) / itemHeight) - const clampedIndex = Math.max(0, Math.min(currentIndex, yearOptions.length - 1)) - const newYear = minYear + clampedIndex - - if (newYear !== selectedYear) { - setSelectedYear(newYear) - } - } - - // 处理年份滚动结束,自动对齐 - const handleYearScrollEnd = () => { - const yearIndex = getYearIndex(selectedYear) - const alignedScrollTop = calculateScrollTop(yearIndex) - // 使用setTimeout确保滚动动画完成后再对齐 - setTimeout(() => { - setYearScrollTop(alignedScrollTop) - }, 100) - } - - // 处理月份滚动 - const handleMonthScroll = (event: any) => { - const scrollTop = event.detail.scrollTop - const itemHeight = 48 - const containerHeight = 216 - const centerOffset = (containerHeight - itemHeight) / 2 - - // 计算当前选中的月份索引 - const currentIndex = Math.round((scrollTop + centerOffset) / itemHeight) - const clampedIndex = Math.max(0, Math.min(currentIndex, monthOptions.length - 1)) - const newMonth = clampedIndex + 1 - - if (newMonth !== selectedMonth) { - setSelectedMonth(newMonth) - } - } - - // 处理月份滚动结束,自动对齐 - const handleMonthScrollEnd = () => { - const monthIndex = getMonthIndex(selectedMonth) - const alignedScrollTop = calculateScrollTop(monthIndex) - // 使用setTimeout确保滚动动画完成后再对齐 - setTimeout(() => { - setMonthScrollTop(alignedScrollTop) - }, 100) - } - - const handleConfirm = () => { - onConfirm(selectedYear, selectedMonth) - onClose() - } - - if (!visible) return null - - return ( - - {/* 拖拽手柄 */} - - - {/* 时间选择器 */} - - {/* 自定义多列选择器 */} - - - {/* 选中项指示器 */} - - - {/* 年份列 */} - - - - {yearOptions.map((option, index) => ( - - {option.text} - - ))} - - - - - {/* 月份列 */} - - - - {monthOptions.map((option, index) => ( - - {option.text} - - ))} - - - - - - - - ) -} - -export default TimePicker diff --git a/src/components/TimePicker/demo.module.scss b/src/components/TimePicker/demo.module.scss deleted file mode 100644 index fa78e7f..0000000 --- a/src/components/TimePicker/demo.module.scss +++ /dev/null @@ -1,81 +0,0 @@ -.demoContainer { - padding: 20px; - text-align: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - min-height: 100vh; - color: white; -} - -.title { - font-size: 24px; - font-weight: 700; - margin-bottom: 10px; - display: block; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); -} - -.subtitle { - font-size: 16px; - margin-bottom: 30px; - display: block; - opacity: 0.9; -} - -.demoButton { - margin: 20px 0; - width: 250px; - height: 50px; - border-radius: 25px; - font-size: 18px; - font-weight: 600; - background: rgba(255, 255, 255, 0.2); - border: 2px solid rgba(255, 255, 255, 0.3); - color: white; - - &:active { - background: rgba(255, 255, 255, 0.3); - transform: scale(0.98); - } -} - -.demoResult { - margin: 30px 0; - padding: 20px; - background: rgba(255, 255, 255, 0.1); - border-radius: 16px; - backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.2); - - text { - font-size: 18px; - font-weight: 600; - color: white; - } -} - -.demoFeatures { - margin-top: 40px; - padding: 20px; - background: rgba(255, 255, 255, 0.1); - opacity: 0.9; - border-radius: 16px; - backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.2); - text-align: left; -} - -.featureTitle { - font-size: 18px; - font-weight: 600; - margin-bottom: 15px; - display: block; - color: white; -} - -.featureItem { - font-size: 14px; - margin: 8px 0; - display: block; - color: rgba(255, 255, 255, 0.9); - line-height: 1.5; -} \ No newline at end of file diff --git a/src/components/TimePicker/demo.tsx b/src/components/TimePicker/demo.tsx deleted file mode 100644 index ca0a2a7..0000000 --- a/src/components/TimePicker/demo.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState } from 'react' -import { View, Button, Text } from '@tarojs/components' -import TimePicker from './TimePicker' -import styles from './demo.module.scss' - -const TimePickerDemo: React.FC = () => { - const [visible, setVisible] = useState(false) - const [selectedTime, setSelectedTime] = useState('') - - const handleConfirm = (year: number, month: number) => { - setSelectedTime(`${year}年${month}月`) - setVisible(false) - } - - return ( - - TimePicker 演示 - 体验"一个一个往下翻"的效果 - - - - {selectedTime && ( - - 已选择: {selectedTime} - - )} - - - 特性说明: - • 每次只显示一个选项 - • 完美居中对齐 - • 平滑滚动动画 - • 触摸结束后自动对齐 - - - setVisible(false)} - onConfirm={handleConfirm} - defaultYear={2024} - defaultMonth={6} - minYear={2020} - maxYear={2030} - /> - - ) -} - -export default TimePickerDemo \ No newline at end of file diff --git a/src/components/TimePicker/index.module.scss b/src/components/TimePicker/index.module.scss deleted file mode 100644 index a2cc061..0000000 --- a/src/components/TimePicker/index.module.scss +++ /dev/null @@ -1,187 +0,0 @@ -/* 时间选择器弹出层样式 */ -.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-wrapper { - position: relative; -} - -.custom-picker { - position: relative; - width: 100%; - height: 216px; - background: #fff; - border-radius: 8px; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - /* 确保只显示一个选项 */ - perspective: 1000px; - /* 水平布局 */ - flex-direction: row; - /* 确保列之间有适当间距 */ - gap: 0; -} - -/* 选中项指示器 */ -.picker-indicator { - 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; - box-shadow: inset 0 0 0 1px rgba(22, 24, 35, 0.1); - /* 确保指示器完美覆盖选中项 */ - margin: 0 20px; - width: calc(100% - 40px); -} - -.picker-column { - flex: 1; - height: 100%; - position: relative; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - /* 水平居中布局 */ - min-width: 0; - /* 确保列之间有适当间距 */ - padding: 0 8px; - - &:first-child { - border-right: 1px solid rgba(0, 0, 0, 0.1); - } - - /* 确保滚动容器正确显示 */ - &::before, - &::after { - content: ''; - position: absolute; - left: 0; - right: 0; - height: 84px; - pointer-events: none; - z-index: 2; - } - - &::before { - top: 0; - background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 100%); - } - - &::after { - bottom: 0; - background: linear-gradient(to top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 100%); - } -} - -.picker-scroll { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - will-change: scroll-position; - -webkit-overflow-scrolling: touch; - /* 确保滚动行为 */ - scroll-snap-type: y mandatory; - /* 优化滚动性能 */ - overscroll-behavior: contain; -} - -.picker-padding { - height: 84px; /* (216 - 48) / 2 = 84px,用于居中对齐 */ - /* 确保padding区域不可见 */ - opacity: 0; - pointer-events: none; -} - -.picker-item { - display: flex; - align-items: center; - justify-content: center; - height: 48px; - width: 100%; - font-size: 16px; - color: #161823; - transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); - position: relative; - will-change: transform, color; - /* 确保每个选项都能正确对齐 */ - scroll-snap-align: center; - /* 水平居中 */ - text-align: center; - - &.picker-item-active { - color: #161823; - font-weight: 600; - transform: scale(1.02); - - .picker-item-text { - color: #161823; - font-weight: 600; - } - } - - &:not(.picker-item-active) { - color: rgba(22, 24, 35, 0.6); - - .picker-item-text { - color: rgba(22, 24, 35, 0.6); - } - } -} - -.picker-item-text { - font-size: 16px; - color: inherit; - text-align: center; - transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); - user-select: none; - width: 100%; - line-height: 48px; - white-space: nowrap; - /* 确保文字完美居中 */ - display: block; - overflow: hidden; - text-overflow: ellipsis; - /* 强制居中对齐 */ - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); -} - -/* 滚动条隐藏 */ -.picker-scroll { - ::-webkit-scrollbar { - width: 0; - background: transparent; - } -} - -/* 移除重复的渐变遮罩代码,已在.picker-column中定义 */ \ No newline at end of file diff --git a/src/components/TimePicker/index.ts b/src/components/TimePicker/index.ts deleted file mode 100644 index 0febefb..0000000 --- a/src/components/TimePicker/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './TimePicker' -export type { TimePickerProps } from './TimePicker' \ No newline at end of file diff --git a/src/components/TimePicker/layout-test.module.scss b/src/components/TimePicker/layout-test.module.scss deleted file mode 100644 index f6e0ea0..0000000 --- a/src/components/TimePicker/layout-test.module.scss +++ /dev/null @@ -1,59 +0,0 @@ -.testContainer { - padding: 20px; - text-align: center; - background: #f8f9fa; - min-height: 100vh; -} - -.testTitle { - font-size: 22px; - font-weight: 700; - margin-bottom: 10px; - display: block; - color: #333; -} - -.testSubtitle { - font-size: 16px; - margin-bottom: 30px; - display: block; - color: #666; -} - -.testInfo { - margin: 20px 0; - padding: 20px; - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - text-align: left; - - text { - font-size: 14px; - margin: 8px 0; - display: block; - color: #555; - line-height: 1.5; - } -} - -.testButton { - margin: 20px 0; - width: 200px; - height: 44px; - border-radius: 22px; - font-size: 16px; - background: #007bff; - border: none; -} - -.testResult { - margin: 20px 0; - color: white; - border-radius: 8px; - - text { - font-size: 16px; - font-weight: 600; - } -} \ No newline at end of file diff --git a/src/components/TimePicker/layout-test.tsx b/src/components/TimePicker/layout-test.tsx deleted file mode 100644 index 80075d9..0000000 --- a/src/components/TimePicker/layout-test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useState } from 'react' -import { View, Text, Button } from '@tarojs/components' -import TimePicker from './TimePicker' -import styles from './layout-test.module.scss' - -const LayoutTest: React.FC = () => { - const [visible, setVisible] = useState(false) - const [selectedTime, setSelectedTime] = useState('') - - const handleConfirm = (year: number, month: number) => { - setSelectedTime(`${year}年${month}月`) - setVisible(false) - } - - return ( - - 布局测试 - 验证年份和月份的水平居中对齐 - - - • 年份和月份应该在同一行显示 - • 两个列应该水平居中对齐 - • 选中项指示器应该完美覆盖两个列 - - - - - {selectedTime && ( - - 选择结果: {selectedTime} - - )} - - setVisible(false)} - onConfirm={handleConfirm} - defaultYear={2024} - defaultMonth={6} - minYear={2020} - maxYear={2030} - /> - - ) -} \ No newline at end of file diff --git a/src/components/TimePicker/test.module.scss b/src/components/TimePicker/test.module.scss deleted file mode 100644 index 594819e..0000000 --- a/src/components/TimePicker/test.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -.container { - padding: 20px; - text-align: center; - background: #f5f5f5; - min-height: 100vh; -} - -.title { - font-size: 20px; - font-weight: 600; - color: #333; - margin-bottom: 30px; - display: block; -} - -.button { - margin: 20px 0; - width: 200px; - height: 44px; - border-radius: 22px; - font-size: 16px; -} - -.result { - margin-top: 30px; - padding: 20px; - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - - text { - font-size: 16px; - color: #333; - font-weight: 500; - } -} \ No newline at end of file diff --git a/src/components/TimePicker/test.tsx b/src/components/TimePicker/test.tsx deleted file mode 100644 index fe8ef7a..0000000 --- a/src/components/TimePicker/test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useState } from 'react' -import { View, Button, Text } from '@tarojs/components' -import TimePicker from './TimePicker' -import styles from './test.module.scss' - -const TimePickerTest: React.FC = () => { - const [visible, setVisible] = useState(false) - const [selectedTime, setSelectedTime] = useState('') - - const handleConfirm = (year: number, month: number) => { - setSelectedTime(`${year}年${month}月`) - setVisible(false) - } - - return ( - - TimePicker 组件测试 - - - - {selectedTime && ( - - 已选择: {selectedTime} - - )} - - setVisible(false)} - onConfirm={handleConfirm} - defaultYear={2024} - defaultMonth={6} - minYear={2020} - maxYear={2030} - /> - - ) -} - -export default TimePickerTest \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 4d84493..2918423 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,7 +8,6 @@ import NumberInterval from "./NumberInterval"; import TimeSelector from "./TimeSelector"; import TitleTextarea from "./TitleTextarea"; import CommonPopup from "./CommonPopup"; -import TimePicker from "./TimePicker/TimePicker"; import { CalendarUI, DialogCalendarCard } from "./Picker"; import CommonDialog from "./CommonDialog"; import PublishMenu from "./PublishMenu/PublishMenu"; @@ -28,7 +27,6 @@ export { TimeSelector, TitleTextarea, CommonPopup, - TimePicker, DialogCalendarCard, CalendarUI, CommonDialog,