This commit is contained in:
oneao committed 2026-04-21 22:12:34 +08:00
1 parent b1348343c9
commit b7f2efc7c3
13 files changed
+662 -456

No files matched your search

+3
View File
@@ -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",
@@ -46,11 +47,13 @@
"expo-wechat": "^0.2.6",
"heroui-native": "^1.0.1",
"lucide-react-native": "^1.8.0",
"moment": "^2.30.1",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-native": "0.83.4",
"react-native-gesture-handler": "~2.30.0",
"react-native-keyboard-controller": "1.20.7",
"react-native-modal": "14.0.0-rc.1",
"react-native-reanimated": "4.2.1",
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.23.0",
+74 -33
View File
@@ -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
@@ -107,6 +110,9 @@ importers:
lucide-react-native:
specifier: ^1.8.0
version: 1.8.0(react-native-svg@15.15.3(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)
moment:
specifier: ^2.30.1
version: 2.30.1
react:
specifier: 19.2.0
version: 19.2.0
@@ -122,6 +128,9 @@ importers:
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)
react-native-modal:
specifier: 14.0.0-rc.1
version: 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-reanimated:
specifier: 4.2.1
version: 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)
@@ -1022,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==}
@@ -1144,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==}
@@ -1504,6 +1497,9 @@ packages:
resolution: {integrity: sha512-wYUdv0rt4MjhKhQloO1AnGDXhZQOFZHDxm86dEtEA0WcsCdVrFdRULFM+rKUC/QQtJW2rS6WBqtBusgtrsDADg==}
engines: {node: '>= 20.19.4'}
'@react-native/normalize-colors@0.73.2':
resolution: {integrity: sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==}
'@react-native/normalize-colors@0.74.89':
resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==}
@@ -1612,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==}
@@ -1712,6 +1704,14 @@ packages:
resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
engines: {node: '>=10.0.0'}
'@yz1311/react-native-wheel-picker@0.4.5':
resolution: {integrity: sha512-07ga/fYdgHYI3JTAd+ntaXcJ1D3VxbgaqCPxABjxigehmFYD3VpBbMMVRhxmZC3oVnpedc+SKJXHTNwGtLriCQ==}
peerDependencies:
deprecated-react-native-prop-types: '*'
moment: '*'
react-native: '*'
react-native-modal: '*'
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
@@ -2134,6 +2134,10 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
deprecated-react-native-prop-types@5.0.0:
resolution: {integrity: sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==}
engines: {node: '>=18'}
destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
@@ -2576,7 +2580,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==}
@@ -2909,84 +2913,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==}
@@ -3271,6 +3263,9 @@ packages:
engines: {node: '>=10'}
hasBin: true
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
@@ -3479,6 +3474,9 @@ packages:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
proxy-from-env@2.1.0:
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
engines: {node: '>=10'}
@@ -3523,6 +3521,9 @@ packages:
react-is@19.2.5:
resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==}
react-native-animatable@1.4.0:
resolution: {integrity: sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==}
react-native-gesture-handler@2.30.1:
resolution: {integrity: sha512-xIUBDo5ktmJs++0fZlavQNvDEE4PsihWhSeJsJtoz4Q6p0MiTM9TgrTgfEgzRR36qGPytFoeq+ShLrVwGdpUdA==}
peerDependencies:
@@ -3565,6 +3566,12 @@ packages:
react-native: '*'
react-native-reanimated: '>=3.0.0'
react-native-modal@14.0.0-rc.1:
resolution: {integrity: sha512-v5pvGyx1FlmBzdHyPqBsYQyS2mIJhVmuXyNo5EarIzxicKhuoul6XasXMviGcXboEUT0dTYWs88/VendojPiVw==}
peerDependencies:
react: '*'
react-native: '>=0.70.0'
react-native-reanimated@4.2.1:
resolution: {integrity: sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==}
peerDependencies:
@@ -5744,6 +5751,8 @@ snapshots:
'@react-native/js-polyfills@0.83.4': {}
'@react-native/normalize-colors@0.73.2': {}
'@react-native/normalize-colors@0.74.89': {}
'@react-native/normalize-colors@0.83.4': {}
@@ -5948,6 +5957,14 @@ snapshots:
'@xmldom/xmldom@0.8.12': {}
'@yz1311/react-native-wheel-picker@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))':
dependencies:
deprecated-react-native-prop-types: 5.0.0
moment: 2.30.1
prop-types: 15.8.1
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
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)
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
@@ -6413,6 +6430,12 @@ snapshots:
depd@2.0.0: {}
deprecated-react-native-prop-types@5.0.0:
dependencies:
'@react-native/normalize-colors': 0.73.2
invariant: 2.2.4
prop-types: 15.8.1
destroy@1.2.0: {}
detect-libc@2.1.2: {}
@@ -7729,6 +7752,8 @@ snapshots:
mkdirp@1.0.4: {}
moment@2.30.1: {}
ms@2.0.0: {}
ms@2.1.3: {}
@@ -7938,6 +7963,12 @@ snapshots:
kleur: 3.0.3
sisteransi: 1.0.5
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
proxy-from-env@2.1.0: {}
query-string@7.1.3:
@@ -7980,6 +8011,10 @@ snapshots:
react-is@19.2.5: {}
react-native-animatable@1.4.0:
dependencies:
prop-types: 15.8.1
react-native-gesture-handler@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):
dependencies:
'@egjs/hammerjs': 2.0.17
@@ -8027,6 +8062,12 @@ snapshots:
react-native-is-edge-to-edge: 1.3.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-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-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):
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-animatable: 1.4.0
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):
dependencies:
react: 19.2.0
@@ -1,25 +0,0 @@
import { memo } from "react";
import {
KeyboardAwareScrollView,
KeyboardAwareScrollViewProps,
} from "react-native-keyboard-controller";
import {
SCROLLABLE_TYPE,
createBottomSheetScrollableComponent,
type BottomSheetScrollViewMethods,
} from "@gorhom/bottom-sheet";
import type { BottomSheetScrollViewProps } from "@gorhom/bottom-sheet/src/components/bottomSheetScrollable/types";
import Reanimated from "react-native-reanimated";
const AnimatedScrollView = Reanimated.createAnimatedComponent(KeyboardAwareScrollView);
const BottomSheetScrollViewComponent = createBottomSheetScrollableComponent<
BottomSheetScrollViewMethods,
BottomSheetScrollViewProps
>(SCROLLABLE_TYPE.SCROLLVIEW, AnimatedScrollView);
const BottomSheetKeyboardAwareScrollView = memo(BottomSheetScrollViewComponent);
BottomSheetKeyboardAwareScrollView.displayName = "BottomSheetKeyboardAwareScrollView";
export default BottomSheetKeyboardAwareScrollView as (
props: BottomSheetScrollViewProps & KeyboardAwareScrollViewProps,
) => ReturnType<typeof BottomSheetKeyboardAwareScrollView>;
+67 -42
View File
@@ -1,54 +1,79 @@
import { BottomSheet, Button, TextField, Input, useBottomSheetAwareHandlers } from "heroui-native";
import { useState, useRef, useEffect } from "react";
import { Keyboard } from "react-native";
import React, { useState } from "react";
import { View, Text } from "react-native";
import { Button } from "heroui-native";
import DateRangePicker from "@/components/DateRangePicker";
import dayjs from "dayjs";
const BottomSheetTextInput = ({ isOpen }: { isOpen: boolean }) => {
const { onFocus, onBlur } = useBottomSheetAwareHandlers();
const inputRef = useRef<any>(null);
export default function RangeDemo() {
const [start, setStart] = useState("");
const [end, setEnd] = useState("");
const [show, setShow] = useState(false);
useEffect(() => {
if (isOpen) {
inputRef.current?.focus();
} else {
inputRef.current?.blur();
}
}, [isOpen]);
// 快捷选择
const setToday = () => {
const today = dayjs().format("YYYY-MM-DD");
setStart(today);
setEnd(today);
};
const setLast7Days = () => {
const end = dayjs();
const start = end.subtract(6, "day");
setStart(start.format("YYYY-MM-DD"));
setEnd(end.format("YYYY-MM-DD"));
};
const setThisMonth = () => {
const start = dayjs().startOf("month");
const end = dayjs().endOf("month");
setStart(start.format("YYYY-MM-DD"));
setEnd(end.format("YYYY-MM-DD"));
};
return (
<TextField>
<Input ref={inputRef} placeholder="Type here..." onFocus={onFocus} onBlur={onBlur} />
</TextField>
);
};
<View className="flex-1 p-4 gap-4">
<Text className="text-lg font-semibold">日期区间选择 Demo</Text>
export default function BottomSheetExample() {
const [isOpen, setIsOpen] = useState(false);
{/* 当前选择展示 */}
<Text className="text-gray-700">开始:{start || "--"}</Text>
<Text className="text-gray-700">结束:{end || "--"}</Text>
return (
<BottomSheet
isOpen={isOpen}
onOpenChange={(open) => {
setIsOpen(open);
{/* 打开选择器 */}
<Button onPress={() => setShow(true)}>
{start && end ? `${start} ~ ${end}` : "选择日期区间"}
</Button>
if (!open) {
Keyboard.dismiss(); // ✅ 关闭时收键盘
}
}}
animation="disable-all"
>
<BottomSheet.Trigger asChild>
<Button variant="secondary" className="mt-30">
Open Bottom Sheet
{/* 快捷选择 */}
<View className="flex-row gap-2 flex-wrap">
<Button size="sm" variant="tertiary" onPress={setToday}>
今天
</Button>
</BottomSheet.Trigger>
<Button size="sm" variant="tertiary" onPress={setLast7Days}>
最近7天
</Button>
<Button size="sm" variant="tertiary" onPress={setThisMonth}>
本月
</Button>
</View>
<BottomSheet.Portal>
<BottomSheet.Overlay />
{/* RangePicker */}
<DateRangePicker
startDate={start}
endDate={end}
show={show}
onClose={() => setShow(false)}
onConfirm={(s, e) => {
// 简单校验(开始不能大于结束)
if (dayjs(s).isAfter(dayjs(e))) {
console.warn("开始时间不能大于结束时间");
return;
}
<BottomSheet.Content enableBlurKeyboardOnGesture keyboardBlurBehavior="restore">
<BottomSheetTextInput isOpen={isOpen} />
</BottomSheet.Content>
</BottomSheet.Portal>
</BottomSheet>
setStart(s);
setEnd(e);
setShow(false);
}}
/>
</View>
);
}
@@ -1,39 +0,0 @@
import { BottomSheet, Button, TextField, Input, useBottomSheetAwareHandlers } from "heroui-native";
import { useState } from "react";
const BottomSheetTextInput = () => {
const { onFocus, onBlur } = useBottomSheetAwareHandlers();
return (
<TextField>
<Input placeholder="Type here..." onFocus={onFocus} onBlur={onBlur} />
</TextField>
);
};
export default function BottomSheetExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<BottomSheet
isOpen={isOpen}
onOpenChange={(open) => {
setIsOpen(open);
}}
>
<BottomSheet.Trigger asChild>
<Button variant="secondary" className="mt-30">
Open Bottom Sheet
</Button>
</BottomSheet.Trigger>
<BottomSheet.Portal>
<BottomSheet.Overlay />
<BottomSheet.Content keyboardBlurBehavior="restore">
<BottomSheetTextInput />
</BottomSheet.Content>
</BottomSheet.Portal>
</BottomSheet>
);
}
+6 -12
View File
@@ -3,11 +3,7 @@ import LayoutView from "@/components/layout/view";
import { loadDataApi } from "@/request/api";
import { View, Text } from "react-native";
import { useFamilyStore } from "@/store";
import {
HouseHeartIcon,
LayoutGridIcon,
LayersIcon,
} from "lucide-react-native";
import { LayoutGridIcon, LayersIcon } from "lucide-react-native";
import { IconAppFinance, IconAppClothes } from "@/components/iconfont";
import { PressableFeedback } from "heroui-native";
@@ -71,15 +67,13 @@ export default function Home() {
<LayoutView>
<LayoutView.ScrollView>
<View className="items-center justify-center">
<Text className="text-4xl font-bold tracking-tight text-gray-900">{family.name}</Text>
<View className="flex-row items-center justify-center gap-2">
<Text className="text-4xl font-bold tracking-tight text-gray-900">
{family.name || "-"}
</Text>
</View>
<View className="items-center mt-3">
<View className="flex-row items-center gap-2 mb-1">
<HouseHeartIcon size={18} color="#6a7282" />
<Text className="text-sm text-gray-500">家庭已陪伴你</Text>
</View>
<View className="flex-row items-center gap-2">
<Text className="text-3xl font-bold text-accent">{duration.days}</Text>
<Text className="text-gray-400">天</Text>
@@ -1,10 +1,24 @@
import React from "react";
import { View, Text } from "react-native";
import DatePicker from "@/components/DatePicker";
import { useState } from "react";
import { Button } from "heroui-native";
export default function Star() {
const [date, setDate] = useState("");
const [showDate, setShowDate] = useState(false);
return (
<View className="flex-1">
<Text className="text-base text-black">Star</Text>
<Button
onPress={() => {
setShowDate(!showDate);
}}
>
测试
</Button>
<DatePicker value={date} show={showDate} />
</View>
);
}
+182 -40
View File
@@ -1,11 +1,17 @@
import { useEffect } from "react";
import { Stack } from "expo-router";
import { Image, Text, View } from "react-native";
import { Button, LinkButton, ControlField, Checkbox, Spinner } from "heroui-native";
import { IconQq } from "@/components/iconfont";
import { IconQq, IconWeixin } from "@/components/iconfont";
import { useState } from "react";
import { FadeIn } from "react-native-reanimated";
import { useUserStore, useFamilyStore } from "@/store";
import { useRouter } from "expo-router";
import ExpoQQ from "expo-qq";
import ExpoWeChat from "expo-wechat";
import { useEvent } from "expo";
import { toast } from "sonner-native";
import { loginQqApi, loginWxApi } from "@/request/api";
export default function Login() {
const router = useRouter();
@@ -14,49 +20,190 @@ export default function Login() {
const [loading, setLoading] = useState(false);
const [isAgreed, setIsAgreed] = useState(true);
const [form, setForm] = useState({
type: 0,
openid: "",
code: "",
});
/**
* {"code": 1000, "data": {"avatar": "http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100",
* "nickname": "Zhang", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo3OTY0NTY3MTI3ODU5ODl9LCJpYXQiOjE3NzY1ODU2MDR9.ZwmN5_0MGrP9dYpn3quK94HXMG3QYKmAq7wnP5X68dY", "userId": 796456712785989}, "isSuccess": true, "message": "success", "track_id": "djIRo23SZlA160zBTH2DX-kxeuLApKTNyZdxqqauSMk"}
*/
// 登录
const onLoginFinished = useEvent(ExpoQQ, "onLoginFinished");
const onGetUserInfo = useEvent(ExpoQQ, "onGetUserInfo");
const authResult = useEvent(ExpoWeChat, "onAuthResult");
function handleTest() {
useEffect(() => {
ExpoQQ.init("102826474", "");
ExpoWeChat.registerApp("wxdab3e21a1f7e392f", "");
}, []);
// 微信登录
async function handleLoginWechat() {
if (loading) {
return;
}
setLoading(true);
// 检查微信是否安装
const isInstalled = await ExpoWeChat.isWXAppInstalled();
if (!isInstalled) {
toast.warning(onLoginFinished?.errorMessage || "请先安装微信");
setLoading(false);
return;
}
setForm((val) => ({
...val,
type: 0,
}));
ExpoWeChat.sendAuthRequest("snsapi_userinfo", "state123");
}
// QQ 登录
async function handleLoginQQ() {
if (loading) {
return;
}
setLoading(true);
setTimeout(() => {
requestAnimationFrame(() => {
userStore.setUserInfo({
id: 796541901389893,
token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo3OTY0NTY3MTI3ODU5ODl9LCJpYXQiOjE3NzY1ODU2MDR9.ZwmN5_0MGrP9dYpn3quK94HXMG3QYKmAq7wnP5X68dY",
nickname: "Zhang",
avatar:
"http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100",
});
userStore.setAuthExpired(false);
console.log(familyStore.inFamily());
// if (familyStore.inFamily()) {
// router.replace("/(tabs)/home");
// } else {
// router.replace("/family/entry");
// return;
// }
familyStore.setCurrentFamily({
family_id: 797139042881605,
});
router.replace("/(tabs)/home");
});
setLoading(false);
}, 500);
setForm((val) => ({
...val,
type: 1,
}));
await ExpoQQ.login(["get_user_info"]);
}
useEffect(() => {
if (!onLoginFinished) {
return;
}
if (!onLoginFinished.success) {
toast.warning(onLoginFinished?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权");
setLoading(false);
return;
}
const fetchUserInfo = async () => {
try {
const tokenInfo = await ExpoQQ.getLoginTokenInfo();
if (!tokenInfo.openId) {
toast.warning(tokenInfo?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权");
setLoading(false);
return;
}
setForm((val) => ({
...val,
openid: String(tokenInfo.openId),
}));
await ExpoQQ.sendGetUserInfoReq();
} catch (e) {
console.log(e);
setLoading(false);
}
};
fetchUserInfo();
}, [onLoginFinished]);
// QQ登录
useEffect(() => {
const handleLogin = async () => {
if (!onGetUserInfo) {
setLoading(false);
return;
}
try {
const loginRes = await loginQqApi({
openid: form.openid,
nickname: onGetUserInfo.nickname || "",
avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2,
});
if (loginRes.isSuccess) {
requestAnimationFrame(() => {
userStore.setUserInfo({
token: loginRes.data.token,
id: loginRes.data.userId,
nickname: loginRes.data.nickname,
avatar: loginRes.data.avatar,
});
userStore.setAuthExpired(false);
if (loginRes.data.family_id === 0) {
router.replace("/family/entry");
} else {
familyStore.setCurrentFamily({
family_id: loginRes.data.family_id,
});
router.replace("/(tabs)/home");
}
});
}
} catch (err) {
console.error("登录接口出错", err);
} finally {
setLoading(false);
}
};
handleLogin();
}, [onGetUserInfo]);
// 微信登录
useEffect(() => {
if (!authResult) return;
const handleLogin = async () => {
if (authResult.errorCode === 0) {
try {
const loginRes = await loginWxApi({
code: authResult.code,
});
if (loginRes.isSuccess) {
requestAnimationFrame(() => {
userStore.setUserInfo({
token: loginRes.data.token,
id: loginRes.data.userId,
nickname: loginRes.data.nickname,
avatar: loginRes.data.avatar,
});
userStore.setAuthExpired(false);
if (loginRes.data.family_id === 0) {
router.replace("/family/entry");
} else {
familyStore.setCurrentFamily({
family_id: loginRes.data.family_id,
});
router.replace("/(tabs)/home");
}
});
}
} catch (err) {
console.error("登录接口出错", err);
} finally {
setLoading(false);
}
} else {
console.log("授权失败:", authResult.errorMessage);
setLoading(false);
}
};
handleLogin();
}, [authResult]);
return (
<>
<Stack.Screen
@@ -75,7 +222,7 @@ export default function Login() {
</View>
<View className="flex flex-col gap-4">
{/* <Button onPress={handleLoginWechat}>
<Button onPress={handleLoginWechat}>
{loading ? (
<Spinner entering={FadeIn.delay(50)} color="white" />
) : (
@@ -87,11 +234,6 @@ export default function Login() {
<Button variant="outline" onPress={handleLoginQQ}>
{loading ? <Spinner entering={FadeIn.delay(50)} /> : <IconQq />}
<Button.Label>QQ</Button.Label>
</Button> */}
<Button variant="outline" onPress={handleTest}>
{loading ? <Spinner entering={FadeIn.delay(50)} /> : <IconQq />}
<Button.Label>Test</Button.Label>
</Button>
</View>
+119
View File
@@ -0,0 +1,119 @@
import { Stack } from "expo-router";
import { Image, Text, View } from "react-native";
import { Button, LinkButton, ControlField, Checkbox, Spinner } from "heroui-native";
import { IconQq } from "@/components/iconfont";
import { useState } from "react";
import { FadeIn } from "react-native-reanimated";
import { useUserStore, useFamilyStore } from "@/store";
import { useRouter } from "expo-router";
export default function Login() {
const router = useRouter();
const userStore = useUserStore();
const familyStore = useFamilyStore();
const [loading, setLoading] = useState(false);
const [isAgreed, setIsAgreed] = useState(true);
/**
* {"code": 1000, "data": {"avatar": "http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100", "family_id": 797225979793477, "nickname": "Zhang", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJfaWQiOjc5NjU0MTkwMTM4OTg5M30sImlhdCI6MTc3Njc3MTcyOX0.JldxjA-0u2hza3qNx20xgUyVBVVV6hQuVuTW1GcJyr8", "userId": 796541901389893}, "isSuccess": true, "message": "success", "track_id": "hI5gEsPHu1LGwkQaJ6lf1dDQby0rzxGVJj0KcNC3Nxw"}
*/
function handleTest() {
if (loading) {
return;
}
setLoading(true);
setTimeout(() => {
requestAnimationFrame(() => {
userStore.setUserInfo({
id: 796541901389893,
token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJfaWQiOjc5NjU0MTkwMTM4OTg5M30sImlhdCI6MTc3Njc3MTcyOX0.JldxjA-0u2hza3qNx20xgUyVBVVV6hQuVuTW1GcJyr8",
nickname: "Zhang",
avatar:
"http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100",
});
userStore.setAuthExpired(false);
console.log(familyStore.inFamily());
// if (familyStore.inFamily()) {
// router.replace("/(tabs)/home");
// } else {
// router.replace("/family/entry");
// return;
// }
familyStore.setCurrentFamily({
family_id: 797225979793477,
});
router.replace("/(tabs)/home");
});
setLoading(false);
}, 500);
}
return (
<>
<Stack.Screen
options={{
animation: "fade",
}}
/>
<View className="flex-1 bg-white px-6 justify-center">
<View className="items-center mb-12">
<Image
source={require("@/static/images/logo.png")}
className="w-20 h-20 mb-4"
resizeMode="contain"
/>
<Text className="text-xl font-semibold text-gray-900">Welcome to Allapp</Text>
</View>
<View className="flex flex-col gap-4">
{/* <Button onPress={handleLoginWechat}>
{loading ? (
<Spinner entering={FadeIn.delay(50)} color="white" />
) : (
<IconWeixin color="white" />
)}
<Button.Label>WeChat</Button.Label>
</Button>
<Button variant="outline" onPress={handleLoginQQ}>
{loading ? <Spinner entering={FadeIn.delay(50)} /> : <IconQq />}
<Button.Label>QQ</Button.Label>
</Button> */}
<Button variant="outline" onPress={handleTest}>
{loading ? <Spinner entering={FadeIn.delay(50)} /> : <IconQq />}
<Button.Label>Test</Button.Label>
</Button>
</View>
<ControlField
isSelected={isAgreed}
onSelectedChange={setIsAgreed}
className="items-start mt-10"
>
<ControlField.Indicator>
<Checkbox className="w-5 h-5 rounded-sm" />
</ControlField.Indicator>
<View className="flex-row flex-wrap flex-1 gap-1">
<Text className="text-sm text-muted">登录即表示你同意我们的</Text>
<LinkButton size="sm">
<LinkButton.Label className="text-accent">用户协议</LinkButton.Label>
</LinkButton>
<Text className="text-sm text-muted">和</Text>
<LinkButton size="sm">
<LinkButton.Label className="text-accent">隐私政策</LinkButton.Label>
</LinkButton>
</View>
</ControlField>
</View>
</>
);
}
-262
View File
@@ -1,262 +0,0 @@
import { useEffect } from "react";
import { Stack } from "expo-router";
import { Image, Text, View } from "react-native";
import { Button, LinkButton, ControlField, Checkbox, Spinner } from "heroui-native";
import { IconQq, IconWeixin } from "@/components/iconfont";
import { useState } from "react";
import { FadeIn } from "react-native-reanimated";
import { useUserStore, useFamilyStore } from "@/store";
import { useRouter } from "expo-router";
import ExpoQQ from "expo-qq";
import ExpoWeChat from "expo-wechat";
import { useEvent } from "expo";
import { toast } from "sonner-native";
import { loginQqApi, loginWxApi } from "@/request/api";
export default function Login() {
const router = useRouter();
const userStore = useUserStore();
const familyStore = useFamilyStore();
const [loading, setLoading] = useState(false);
const [isAgreed, setIsAgreed] = useState(true);
const [form, setForm] = useState({
type: 0,
openid: "",
code: "",
});
// 登录
const onLoginFinished = useEvent(ExpoQQ, "onLoginFinished");
const onGetUserInfo = useEvent(ExpoQQ, "onGetUserInfo");
const authResult = useEvent(ExpoWeChat, "onAuthResult");
useEffect(() => {
ExpoQQ.init("102826474", "");
ExpoWeChat.registerApp("wxdab3e21a1f7e392f", "");
}, []);
// 微信登录
async function handleLoginWechat() {
if (loading) {
return;
}
setLoading(true);
// 检查微信是否安装
const isInstalled = await ExpoWeChat.isWXAppInstalled();
if (!isInstalled) {
toast.warning(onLoginFinished?.errorMessage || "请先安装微信");
setLoading(false);
return;
}
setForm((val) => ({
...val,
type: 0,
}));
ExpoWeChat.sendAuthRequest("snsapi_userinfo", "state123");
}
// QQ 登录
async function handleLoginQQ() {
if (loading) {
return;
}
setLoading(true);
setForm((val) => ({
...val,
type: 1,
}));
await ExpoQQ.login(["get_user_info"]);
}
useEffect(() => {
if (!onLoginFinished) {
return;
}
if (!onLoginFinished.success) {
toast.warning(onLoginFinished?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权");
setLoading(false);
return;
}
const fetchUserInfo = async () => {
try {
const tokenInfo = await ExpoQQ.getLoginTokenInfo();
if (!tokenInfo.openId) {
toast.warning(tokenInfo?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权");
setLoading(false);
return;
}
setForm((val) => ({
...val,
openid: String(tokenInfo.openId),
}));
await ExpoQQ.sendGetUserInfoReq();
} catch (e) {
console.log(e);
setLoading(false);
}
};
fetchUserInfo();
}, [onLoginFinished]);
// QQ登录
useEffect(() => {
const handleLogin = async () => {
if (!onGetUserInfo) {
setLoading(false);
return;
}
try {
const loginRes = await loginQqApi({
openid: form.openid,
nickname: onGetUserInfo.nickname || "",
avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2,
});
if (loginRes.isSuccess) {
requestAnimationFrame(() => {
userStore.setUserInfo({
token: loginRes.data.token,
id: loginRes.data.userId,
nickname: loginRes.data.nickname,
avatar: loginRes.data.avatar,
});
userStore.setAuthExpired(false);
if (loginRes.data.family_id === 0) {
router.replace("/family/entry");
} else {
familyStore.setCurrentFamily({
family_id: loginRes.data.family_id,
});
router.replace("/(tabs)/home");
}
});
}
} catch (err) {
console.error("登录接口出错", err);
} finally {
setLoading(false);
}
};
handleLogin();
}, [onGetUserInfo]);
// 微信登录
useEffect(() => {
if (!authResult) return;
const handleLogin = async () => {
if (authResult.errorCode === 0) {
try {
const loginRes = await loginWxApi({
code: authResult.code,
});
if (loginRes.isSuccess) {
requestAnimationFrame(() => {
userStore.setUserInfo({
token: loginRes.data.token,
id: loginRes.data.userId,
nickname: loginRes.data.nickname,
avatar: loginRes.data.avatar,
});
userStore.setAuthExpired(false);
if (loginRes.data.family_id === 0) {
router.replace("/family/entry");
} else {
familyStore.setCurrentFamily({
family_id: loginRes.data.family_id,
});
router.replace("/(tabs)/home");
}
});
}
} catch (err) {
console.error("登录接口出错", err);
} finally {
setLoading(false);
}
} else {
console.log("授权失败:", authResult.errorMessage);
setLoading(false);
}
};
handleLogin();
}, [authResult]);
return (
<>
<Stack.Screen
options={{
animation: "fade",
}}
/>
<View className="flex-1 bg-white px-6 justify-center">
<View className="items-center mb-12">
<Image
source={require("@/static/images/logo.png")}
className="w-20 h-20 mb-4"
resizeMode="contain"
/>
<Text className="text-xl font-semibold text-gray-900">Welcome to Allapp</Text>
</View>
<View className="flex flex-col gap-4">
<Button onPress={handleLoginWechat}>
{loading ? (
<Spinner entering={FadeIn.delay(50)} color="white" />
) : (
<IconWeixin color="white" />
)}
<Button.Label>WeChat</Button.Label>
</Button>
<Button variant="outline" onPress={handleLoginQQ}>
{loading ? <Spinner entering={FadeIn.delay(50)} /> : <IconQq />}
<Button.Label>QQ</Button.Label>
</Button>
</View>
<ControlField
isSelected={isAgreed}
onSelectedChange={setIsAgreed}
className="items-start mt-10"
>
<ControlField.Indicator>
<Checkbox className="w-5 h-5 rounded-sm" />
</ControlField.Indicator>
<View className="flex-row flex-wrap flex-1 gap-1">
<Text className="text-sm text-muted">登录即表示你同意我们的</Text>
<LinkButton size="sm">
<LinkButton.Label className="text-accent">用户协议</LinkButton.Label>
</LinkButton>
<Text className="text-sm text-muted">和</Text>
<LinkButton size="sm">
<LinkButton.Label className="text-accent">隐私政策</LinkButton.Label>
</LinkButton>
</View>
</ControlField>
</View>
</>
);
}
@@ -0,0 +1,97 @@
import { DatePicker as YzDatePicker } from "@yz1311/react-native-wheel-picker";
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";
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 {
value: Date | string;
show?: boolean;
onClose?: () => void;
onConfirm?: (value: string) => void;
mode?: DatePickerMode;
format?: DateFormatType;
}
const defaultFormatMap: Record<DatePickerMode, DateFormatType> = {
year: "YYYY",
month: "YYYY-MM",
date: "YYYY-MM-DD",
time: "HH:mm:ss",
datetime: "YYYY-MM-DD HH:mm",
};
export default function DatePicker({
value,
show = false,
onClose,
onConfirm,
mode = "date",
format,
}: DatePickerProps) {
const [tempDate, setTempDate] = useState<Date>(toDate(value));
const [isOpen, setIsOpen] = useState(false);
const finalFormat = format || defaultFormatMap[mode];
useEffect(() => {
if (show) {
setTempDate(toDate(value));
setIsOpen(true);
} else {
setIsOpen(false);
}
}, [show, value]);
const handleClose = () => {
setIsOpen(false);
onClose?.();
};
const handleCancel = () => {
setTempDate(toDate(value));
handleClose();
};
const handleConfirm = () => {
const formatted = dayjs(tempDate).format(finalFormat);
onConfirm?.(formatted);
handleClose();
};
return (
<BottomSheet isOpen={isOpen} onOpenChange={(v) => !v && handleClose()}>
<BottomSheet.Portal>
<BottomSheet.Overlay />
<BottomSheet.Content>
<View style={{ width: "100%", alignItems: "center" }}>
<View className="flex-row items-center justify-between w-full">
<Button size="sm" variant="tertiary" onPress={handleCancel}>
取消
</Button>
<Text className="text-gray-800 text-base font-medium">选择时间</Text>
<Button size="sm" onPress={handleConfirm}>
确认
</Button>
</View>
<YzDatePicker
showHeader={false}
date={tempDate}
mode={mode}
isModal={false}
onPickerCancel={handleCancel}
onPickerConfirm={handleConfirm}
onDateChange={(d: Date) => setTempDate(d)}
/>
</View>
</BottomSheet.Content>
</BottomSheet.Portal>
</BottomSheet>
);
}
@@ -0,0 +1,97 @@
import React, { useState, useEffect, useMemo } from "react";
import { DateRangePicker as YzDateRangePicker } from "@yz1311/react-native-wheel-picker";
import { BottomSheet, Button } from "heroui-native";
import { View, Text } from "react-native";
import dayjs from "dayjs";
import { toDate } from "@/utils/time";
interface DateRangePickerProps {
startDate: Date | string;
endDate: Date | string;
show?: boolean;
onClose?: () => void;
onConfirm?: (start: string, end: string) => void;
}
export default function DateRangePicker({
startDate,
endDate,
show = false,
onClose,
onConfirm,
}: DateRangePickerProps) {
const [tempStart, setTempStart] = useState<Date | undefined>(() => toDate(startDate));
const [tempEnd, setTempEnd] = useState<Date | undefined>(() => toDate(endDate));
useEffect(() => {
setTempStart(toDate(startDate));
setTempEnd(toDate(endDate));
}, [startDate, endDate]);
const isOpen = show;
const handleClose = () => {
onClose?.();
};
const handleCancel = () => {
setTempStart(toDate(startDate));
setTempEnd(toDate(endDate));
handleClose();
};
const handleConfirm = () => {
const start = dayjs(tempStart).format("YYYY-MM-DD");
const end = dayjs(tempEnd).format("YYYY-MM-DD");
onConfirm?.(start, end);
handleClose();
};
const key = useMemo(
() => `${dayjs(startDate).format("YYYY-MM-DD")}_${dayjs(endDate).format("YYYY-MM-DD")}`,
[startDate, endDate],
);
return (
<BottomSheet
isOpen={isOpen}
onOpenChange={(v) => {
if (!v) handleClose();
}}
>
<BottomSheet.Portal>
<BottomSheet.Overlay />
<BottomSheet.Content>
<View style={{ width: "100%" }}>
{/* Header */}
<View className="flex-row items-center justify-between w-full">
<Button size="sm" variant="tertiary" onPress={handleCancel}>
取消
</Button>
<Text className="text-gray-800 text-base font-medium">选择日期区间</Text>
<Button size="sm" onPress={handleConfirm}>
确认
</Button>
</View>
{/* Range Picker */}
<YzDateRangePicker
key={`${dayjs(startDate).valueOf()}_${dayjs(endDate).valueOf()}`}
showHeader={false}
startDate={tempStart}
endDate={tempEnd}
onValueChange={(start: Date, end: Date) => {
setTempStart(start);
setTempEnd(end);
}}
/>
</View>
</BottomSheet.Content>
</BottomSheet.Portal>
</BottomSheet>
);
}