From bddbb49e1985dbb4273824058ae8aadda234a59f Mon Sep 17 00:00:00 2001 From: oneao Date: Wed, 22 Apr 2026 22:05:07 +0800 Subject: [PATCH] u --- code/app/app-rn4/package.json | 3 +- code/app/app-rn4/pnpm-lock.yaml | 54 +++---- .../app/app-rn4/src/app/(tabs)/chat/index.tsx | 59 +++++-- code/app/app-rn4/src/app/_layout.tsx | 4 +- code/app/app-rn4/src/components/DateRange.tsx | 97 ------------ code/app/app-rn4/src/components/DateTime.tsx | 48 ++---- .../app-rn4/src/components/DateTimeRange.tsx | 145 ++++++++++++++++++ code/app/app-rn4/src/utils/time.ts | 8 +- 8 files changed, 238 insertions(+), 180 deletions(-) delete mode 100644 code/app/app-rn4/src/components/DateRange.tsx create mode 100644 code/app/app-rn4/src/components/DateTimeRange.tsx diff --git a/code/app/app-rn4/package.json b/code/app/app-rn4/package.json index 13c8556b..526e1d33 100644 --- a/code/app/app-rn4/package.json +++ b/code/app/app-rn4/package.json @@ -20,6 +20,7 @@ "@react-navigation/bottom-tabs": "^7.15.5", "@react-navigation/elements": "^2.9.10", "@react-navigation/native": "^7.1.33", + "@yz1311/react-native-wheel-picker": "^0.4.5", "alova": "^3.5.1", "axios": "^1.15.0", "dayjs": "^1.11.20", @@ -50,6 +51,7 @@ "react-dom": "19.2.0", "react-native": "0.83.4", "react-native-gesture-handler": "~2.30.0", + "react-native-image-crop-picker": "^0.51.1", "react-native-keyboard-controller": "1.20.7", "react-native-reanimated": "4.2.1", "react-native-safe-area-context": "~5.6.2", @@ -69,7 +71,6 @@ }, "devDependencies": { "@types/react": "~19.2.2", - "@yz1311/react-native-wheel-picker": "^0.4.5", "babel-preset-expo": "^55.0.16", "oxfmt": "^0.44.0", "oxlint": "^1.59.0", diff --git a/code/app/app-rn4/pnpm-lock.yaml b/code/app/app-rn4/pnpm-lock.yaml index 23a925f1..f8b6b9ba 100644 --- a/code/app/app-rn4/pnpm-lock.yaml +++ b/code/app/app-rn4/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@react-navigation/native': specifier: ^7.1.33 version: 7.2.2(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@yz1311/react-native-wheel-picker': + specifier: ^0.4.5 + version: 0.4.5(deprecated-react-native-prop-types@5.0.0)(moment@2.30.1)(react-native-modal@14.0.0-rc.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) alova: specifier: ^3.5.1 version: 3.5.1 @@ -119,6 +122,9 @@ importers: react-native-gesture-handler: specifier: ~2.30.0 version: 2.30.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react-native-image-crop-picker: + specifier: ^0.51.1 + version: 0.51.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) react-native-keyboard-controller: specifier: 1.20.7 version: 1.20.7(react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) @@ -171,9 +177,6 @@ importers: '@types/react': specifier: ~19.2.2 version: 19.2.14 - '@yz1311/react-native-wheel-picker': - specifier: ^0.4.5 - version: 0.4.5(deprecated-react-native-prop-types@5.0.0)(moment@2.30.1)(react-native-modal@14.0.0-rc.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) babel-preset-expo: specifier: ^55.0.16 version: 55.0.17(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.15)(react-refresh@0.14.2) @@ -1028,56 +1031,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.44.0': resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.44.0': resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.44.0': resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.44.0': resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.44.0': resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.44.0': resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.44.0': resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/binding-openharmony-arm64@0.44.0': resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==} @@ -1150,56 +1145,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.60.0': resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.60.0': resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.60.0': resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.60.0': resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.60.0': resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.60.0': resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.60.0': resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.60.0': resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==} @@ -1621,28 +1608,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -2601,7 +2584,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -2937,84 +2920,72 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-gnu@1.31.1: resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -3583,6 +3554,12 @@ packages: react-native: '*' react-native-svg: '>=9.7.0' + react-native-image-crop-picker@0.51.1: + resolution: {integrity: sha512-GIFRyXJgv1dPceKd/hraK9q9V38v45rSg2ONR6RiSePcOJemkHpc/PMU86pq6lWPilDYHSxbZmea2pNMk85ayw==} + peerDependencies: + react: '*' + react-native: '*' + react-native-is-edge-to-edge@1.2.1: resolution: {integrity: sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==} peerDependencies: @@ -8092,6 +8069,11 @@ snapshots: transitivePeerDependencies: - supports-color + react-native-image-crop-picker@0.51.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) + react-native-is-edge-to-edge@1.2.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 diff --git a/code/app/app-rn4/src/app/(tabs)/chat/index.tsx b/code/app/app-rn4/src/app/(tabs)/chat/index.tsx index 18fa97ba..fcd72187 100644 --- a/code/app/app-rn4/src/app/(tabs)/chat/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/chat/index.tsx @@ -1,13 +1,52 @@ -import React from "react"; -import LayoutView from "@/components/layout/view"; -import { Text } from "react-native"; +import { useState } from "react"; +import { Alert, Button, Image, View, StyleSheet } from "react-native"; +import * as ImagePicker from "expo-image-picker"; + +export default function ImagePickerExample() { + const [image, setImage] = useState(null); + + const pickImage = async () => { + // No permissions request is necessary for launching the image library. + // Manually request permissions for videos on iOS when `allowsEditing` is set to `false` + // and `videoExportPreset` is `'Passthrough'` (the default), ideally before launching the picker + // so the app users aren't surprised by a system dialog after picking a video. + // See "Invoke permissions for videos" sub section for more details. + const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); + + if (!permissionResult.granted) { + Alert.alert("Permission required", "Permission to access the media library is required."); + return; + } + + let result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images", "videos"], + allowsEditing: true, + quality: 1, + }); + + console.log(result); + + if (!result.canceled) { + setImage(result.assets[0].uri); + } + }; -export default function Chat() { return ( - - - Hello - - + + - - 选择日期区间 - - - - - {/* Range Picker */} - { - setTempStart(start); - setTempEnd(end); - }} - /> - - - - - ); -} diff --git a/code/app/app-rn4/src/components/DateTime.tsx b/code/app/app-rn4/src/components/DateTime.tsx index 668074ac..8881b59a 100644 --- a/code/app/app-rn4/src/components/DateTime.tsx +++ b/code/app/app-rn4/src/components/DateTime.tsx @@ -3,9 +3,11 @@ import dayjs from "dayjs"; import { BottomSheet, Button } from "heroui-native"; import { useState, useEffect } from "react"; import { toDate } from "@/utils/time"; -import { View, Text } from "react-native"; +import { View } from "react-native"; +import { CheckIcon } from "lucide-react-native"; export type DatePickerMode = "year" | "month" | "date" | "time" | "datetime"; + export type DateFormatType = "YYYY" | "YYYY-MM" | "YYYY-MM-DD" | "HH:mm:ss" | "YYYY-MM-DD HH:mm"; interface DatePickerProps { @@ -25,7 +27,7 @@ const defaultFormatMap: Record = { datetime: "YYYY-MM-DD HH:mm", }; -export default function DatePicker({ +export default function DateTime({ value, show = false, onClose, @@ -33,62 +35,46 @@ export default function DatePicker({ mode = "date", format, }: DatePickerProps) { - const [tempDate, setTempDate] = useState(toDate(value)); - const [isOpen, setIsOpen] = useState(false); + const finalFormat = format ?? defaultFormatMap[mode] ?? "YYYY-MM-DD"; - const finalFormat = format || defaultFormatMap[mode]; + const [tempDate, setTempDate] = useState(toDate(value) ?? new Date()); useEffect(() => { if (show) { - setTempDate(toDate(value)); - setIsOpen(true); - } else { - setIsOpen(false); + setTempDate(toDate(value) ?? new Date()); } }, [show, value]); const handleClose = () => { - setIsOpen(false); onClose?.(); }; - const handleCancel = () => { - setTempDate(toDate(value)); - handleClose(); - }; - const handleConfirm = () => { - const formatted = dayjs(tempDate).format(finalFormat); - onConfirm?.(formatted); + if (!tempDate) return; + + onConfirm?.(dayjs(tempDate).format(finalFormat)); handleClose(); }; return ( - !v && handleClose()}> + !v && handleClose()}> + - - - - 选择时间 - - - setTempDate(d)} /> + + diff --git a/code/app/app-rn4/src/components/DateTimeRange.tsx b/code/app/app-rn4/src/components/DateTimeRange.tsx new file mode 100644 index 00000000..0b3e029e --- /dev/null +++ b/code/app/app-rn4/src/components/DateTimeRange.tsx @@ -0,0 +1,145 @@ +import React, { useState, useEffect } from "react"; +import { DatePicker as YzDatePicker } from "@yz1311/react-native-wheel-picker"; +import { BottomSheet, Button } from "heroui-native"; +import { View, Text, TouchableOpacity } from "react-native"; +import dayjs from "dayjs"; +import { toDate } from "@/utils/time"; +import { CheckIcon } from "lucide-react-native"; + +interface DateRangePickerProps { + startDate?: Date | string; + endDate?: Date | string; + show?: boolean; + onClose?: () => void; + onConfirm?: (start: string, end: string) => void; +} + +export default function DateTimeRange({ + startDate, + endDate, + show = false, + onClose, + onConfirm, +}: DateRangePickerProps) { + const [tempStart, setTempStart] = useState(undefined); + const [tempEnd, setTempEnd] = useState(undefined); + + const [active, setActive] = useState<"start" | "end">("start"); + + // ✅ 每次打开重置 + useEffect(() => { + if (show) { + setTempStart(toDate(startDate) ?? new Date()); + setTempEnd(toDate(endDate) ?? undefined); + setActive("start"); + } + }, [show, startDate, endDate]); + + const handleClose = () => { + onClose?.(); + }; + + const handleConfirm = () => { + if (!tempStart) return; + + onConfirm?.( + dayjs(tempStart).format("YYYY-MM-DD"), + tempEnd ? dayjs(tempEnd).format("YYYY-MM-DD") : "", + ); + + handleClose(); + }; + + // ❗ start 修改 → 清空 end + const handleStartChange = (date: Date) => { + setTempStart(date); + setTempEnd(undefined); + }; + + // ❗ end 修改 + const handleEndChange = (date: Date) => { + if (!tempStart) return; + setTempEnd(date); + }; + + const formatDate = (date?: Date) => (date ? dayjs(date).format("YYYY-MM-DD") : "请选择"); + + return ( + !v && handleClose()}> + + + + + + {/* 顶部选择区 */} + + {/* start */} + setActive("start")}> + 开始时间 + + {formatDate(tempStart)} + + + + + + {/* end */} + { + if (!tempStart) return; + + // ✅ 核心优化:未选 end → 自动补 start + if (!tempEnd) { + setTempEnd(tempStart); + } + + setActive("end"); + }} + > + 结束时间 + + {formatDate(tempEnd)} + + + + + {/* Picker */} + {active === "start" ? ( + + ) : ( + + )} + + {/* confirm */} + + + + + + ); +} diff --git a/code/app/app-rn4/src/utils/time.ts b/code/app/app-rn4/src/utils/time.ts index 0f9df483..b2da2b93 100644 --- a/code/app/app-rn4/src/utils/time.ts +++ b/code/app/app-rn4/src/utils/time.ts @@ -33,14 +33,14 @@ export function getCurrentYear(): string { } /** 字符串 / 数字 / Date → Date(无效返回 null) */ -export function toDate(input: string | number | Date | undefined | null): Date { - if (!input) return new Date(); +export function toDate(input: string | number | Date | undefined | null): Date | undefined { + if (!input) return undefined; + if (input instanceof Date) return input; const d = dayjs(input); - return d.isValid() ? d.toDate() : new Date(); + return d.isValid() ? d.toDate() : undefined; } - /** Date → YYYY-MM-DD */ export function toDateString(date: string | number | Date): string { const d = dayjs(toDate(date));