u
This commit is contained in:
1 parent
33113f279c
commit
c2c5cc92af
16 files changed
+864
-352
No files matched your search
@@ -16,6 +16,7 @@
|
||||
"@gluestack/ui-next-adapter": "^3.0.3",
|
||||
"@legendapp/motion": "^2.4.0",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-community/datetimepicker": "8.4.4",
|
||||
"@react-native-segmented-control/segmented-control": "2.5.7",
|
||||
"alova": "^3.3.4",
|
||||
"axios": "^1.13.2",
|
||||
@@ -37,14 +38,14 @@
|
||||
"react-native": "0.81.5",
|
||||
"react-native-css-interop": "^0.2.1",
|
||||
"react-native-dates-picker": "^0.2.0",
|
||||
"react-native-drawer-layout": "^4.2.0",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
||||
"react-native-keyboard-controller": "1.18.5",
|
||||
"react-native-modal-datetime-picker": "^18.0.0",
|
||||
"react-native-reanimated": "~4.1.3",
|
||||
"react-native-safe-area-context": "~5.6.1",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-ui-datepicker": "^3.1.2",
|
||||
"react-native-web": "^0.21.0",
|
||||
"react-native-worklets": "0.5.1",
|
||||
"zustand": "^5.0.8"
|
||||
|
||||
+67
-53
@@ -26,6 +26,9 @@ importers:
|
||||
'@react-native-async-storage/async-storage':
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||
'@react-native-community/datetimepicker':
|
||||
specifier: 8.4.4
|
||||
version: 8.4.4(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
'@react-native-segmented-control/segmented-control':
|
||||
specifier: 2.5.7
|
||||
version: 2.5.7(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
@@ -89,15 +92,18 @@ importers:
|
||||
react-native-dates-picker:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-drawer-layout:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-gesture-handler:
|
||||
specifier: ~2.28.0
|
||||
version: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-keyboard-aware-scroll-view:
|
||||
specifier: ^0.9.5
|
||||
version: 0.9.5(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||
react-native-keyboard-controller:
|
||||
specifier: 1.18.5
|
||||
version: 1.18.5(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-modal-datetime-picker:
|
||||
specifier: ^18.0.0
|
||||
version: 18.0.0(@react-native-community/datetimepicker@8.4.4(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||
react-native-reanimated:
|
||||
specifier: ~4.1.3
|
||||
version: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
@@ -110,9 +116,6 @@ importers:
|
||||
react-native-svg:
|
||||
specifier: 15.12.1
|
||||
version: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-ui-datepicker:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-web:
|
||||
specifier: ^0.21.0
|
||||
version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -1606,6 +1609,19 @@ packages:
|
||||
peerDependencies:
|
||||
react-native: ^0.0.0-0 || >=0.65 <1.0
|
||||
|
||||
'@react-native-community/datetimepicker@8.4.4':
|
||||
resolution: {integrity: sha512-bc4ZixEHxZC9/qf5gbdYvIJiLZ5CLmEsC3j+Yhe1D1KC/3QhaIfGDVdUcid0PdlSoGOSEq4VlB93AWyetEyBSQ==}
|
||||
peerDependencies:
|
||||
expo: '>=52.0.0'
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
react-native-windows: '*'
|
||||
peerDependenciesMeta:
|
||||
expo:
|
||||
optional: true
|
||||
react-native-windows:
|
||||
optional: true
|
||||
|
||||
'@react-native-segmented-control/segmented-control@2.5.7':
|
||||
resolution: {integrity: sha512-l84YeVX8xAU3lvOJSvV4nK/NbGhIm2gBfveYolwaoCbRp+/SLXtc6mYrQmM9ScXNwU14mnzjQTpTHWl5YPnkzQ==}
|
||||
peerDependencies:
|
||||
@@ -3578,9 +3594,6 @@ packages:
|
||||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
jalali-plugin-dayjs@1.1.4:
|
||||
resolution: {integrity: sha512-d62QGMTufGQ1TSov4a85gBU58PA8aL6wVQs9BnC3+d/VA+v4Sq0OqM3o6RoY+/VZjliYJroGIPwlSVf7uLnZ3w==}
|
||||
|
||||
jest-environment-node@29.7.0:
|
||||
resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -3757,48 +3770,56 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.27.0:
|
||||
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.27.0:
|
||||
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.27.0:
|
||||
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.27.0:
|
||||
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
||||
@@ -4654,6 +4675,14 @@ packages:
|
||||
react: '>=17.0.0'
|
||||
react-native: '*'
|
||||
|
||||
react-native-drawer-layout@4.2.0:
|
||||
resolution: {integrity: sha512-XFGK5RMcVhEqr2F4iH/cTXrcTm1uK3gIBNFPqHNO7rCXuXZTPBbQYA5yc9/Lqgw2KNQ3sAeHKvW6/WoTURo/eA==}
|
||||
peerDependencies:
|
||||
react: '>= 18.2.0'
|
||||
react-native: '*'
|
||||
react-native-gesture-handler: '>= 2.0.0'
|
||||
react-native-reanimated: '>= 2.0.0'
|
||||
|
||||
react-native-gesture-handler@2.28.0:
|
||||
resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==}
|
||||
peerDependencies:
|
||||
@@ -4668,22 +4697,12 @@ packages:
|
||||
react-native: '*'
|
||||
react-native-svg: '>=9.7.0'
|
||||
|
||||
react-native-iphone-x-helper@1.3.1:
|
||||
resolution: {integrity: sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==}
|
||||
peerDependencies:
|
||||
react-native: '>=0.42.0'
|
||||
|
||||
react-native-is-edge-to-edge@1.2.1:
|
||||
resolution: {integrity: sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-keyboard-aware-scroll-view@0.9.5:
|
||||
resolution: {integrity: sha512-XwfRn+T/qBH9WjTWIBiJD2hPWg0yJvtaEw6RtPCa5/PYHabzBaWxYBOl0usXN/368BL1XktnZPh8C2lmTpOREA==}
|
||||
peerDependencies:
|
||||
react-native: '>=0.48.4'
|
||||
|
||||
react-native-keyboard-controller@1.18.5:
|
||||
resolution: {integrity: sha512-wbYN6Tcu3G5a05dhRYBgjgd74KqoYWuUmroLpigRg9cXy5uYo7prTMIvMgvLtARQtUF7BOtFggUnzgoBOgk0TQ==}
|
||||
peerDependencies:
|
||||
@@ -4691,6 +4710,12 @@ packages:
|
||||
react-native: '*'
|
||||
react-native-reanimated: '>=3.0.0'
|
||||
|
||||
react-native-modal-datetime-picker@18.0.0:
|
||||
resolution: {integrity: sha512-0jdvhhraZQlRACwr7pM6vmZ2kxgzJ4CpnmV6J3TVA6MrXMXK6Zo/upRBKkRp0+fTOiKuNblzesA2U59rYo6SGA==}
|
||||
peerDependencies:
|
||||
'@react-native-community/datetimepicker': '>=6.7.0'
|
||||
react-native: '>=0.65.0'
|
||||
|
||||
react-native-reanimated@4.1.5:
|
||||
resolution: {integrity: sha512-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q==}
|
||||
peerDependencies:
|
||||
@@ -4717,13 +4742,6 @@ packages:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-ui-datepicker@3.1.2:
|
||||
resolution: {integrity: sha512-1yukCKMLUuVb5daYOgb4mLZqpynZ6GMQyNF2NPwpiDB5F2D/NGpeOmoYb7qFwDrTNwr+m4IemNUa0TlYIFktlg==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-web@0.21.2:
|
||||
resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==}
|
||||
peerDependencies:
|
||||
@@ -5124,9 +5142,6 @@ packages:
|
||||
tailwind-merge@1.14.0:
|
||||
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
|
||||
|
||||
tailwind-merge@3.4.0:
|
||||
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
||||
|
||||
tailwind-variants@0.1.20:
|
||||
resolution: {integrity: sha512-AMh7x313t/V+eTySKB0Dal08RHY7ggYK0MSn/ad8wKWOrDUIzyiWNayRUm2PIJ4VRkvRnfNuyRuKbLV3EN+ewQ==}
|
||||
engines: {node: '>=16.x', pnpm: '>=7.x'}
|
||||
@@ -7634,6 +7649,14 @@ snapshots:
|
||||
merge-options: 3.0.4
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
'@react-native-community/datetimepicker@8.4.4(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
invariant: 2.2.4
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
optionalDependencies:
|
||||
expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
'@react-native-segmented-control/segmented-control@2.5.7(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -9953,8 +9976,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jalali-plugin-dayjs@1.1.4: {}
|
||||
|
||||
jest-environment-node@29.7.0:
|
||||
dependencies:
|
||||
'@jest/environment': 29.7.0
|
||||
@@ -11413,6 +11434,15 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
react-native-drawer-layout@4.2.0(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
use-latest-callback: 0.2.6(react@19.1.0)
|
||||
|
||||
react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@egjs/hammerjs': 2.0.17
|
||||
@@ -11436,21 +11466,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
react-native-iphone-x-helper@1.3.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
|
||||
dependencies:
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
react-native-is-edge-to-edge@1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
react-native-keyboard-aware-scroll-view@0.9.5(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
react-native-iphone-x-helper: 1.3.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||
|
||||
react-native-keyboard-controller@1.18.5(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -11458,6 +11478,12 @@ snapshots:
|
||||
react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
react-native-modal-datetime-picker@18.0.0(@react-native-community/datetimepicker@8.4.4(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
|
||||
dependencies:
|
||||
'@react-native-community/datetimepicker': 8.4.4(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
prop-types: 15.8.1
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
@@ -11488,16 +11514,6 @@ snapshots:
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
warn-once: 0.1.1
|
||||
|
||||
react-native-ui-datepicker@3.1.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
dayjs: 1.11.19
|
||||
jalali-plugin-dayjs: 1.1.4
|
||||
lodash: 4.17.21
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||
tailwind-merge: 3.4.0
|
||||
|
||||
react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -11951,8 +11967,6 @@ snapshots:
|
||||
|
||||
tailwind-merge@1.14.0: {}
|
||||
|
||||
tailwind-merge@3.4.0: {}
|
||||
|
||||
tailwind-variants@0.1.20(tailwindcss@3.4.18(yaml@2.8.1)):
|
||||
dependencies:
|
||||
tailwind-merge: 1.14.0
|
||||
|
||||
@@ -1,42 +1,48 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { useState } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import DateTimePicker from 'react-native-dates-picker'
|
||||
// DemoADatePicker.tsx
|
||||
import React, { useState } from 'react'
|
||||
import { Button, ScrollView, StyleSheet } from 'react-native'
|
||||
import { ADateTimeDrawer } from '@/components/common/ao'
|
||||
|
||||
export default function App() {
|
||||
export default function DemoADatePicker() {
|
||||
const [show, setShow] = useState(false)
|
||||
const [date, setDate] = useState(dayjs())
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<DateTimePicker
|
||||
mode="single"
|
||||
date={date}
|
||||
selectedItemColor="#fff"
|
||||
selectedTextStyle={{
|
||||
fontWeight: 'bold',
|
||||
color: '#00d279',
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<ADateTimeDrawer
|
||||
isOpen={show}
|
||||
defaultDate={date}
|
||||
modeType="year-month"
|
||||
onConfirm={(val) => {
|
||||
console.log(val)
|
||||
}}
|
||||
headerTextStyle={{
|
||||
fontSize: 18,
|
||||
onClose={() => {
|
||||
setShow(false)
|
||||
}}
|
||||
headerButtonStyle={{
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
headerButtonColor="#00d279"
|
||||
headerButtonSize={18}
|
||||
headerButtonsPosition="right"
|
||||
onChange={params => setDate(params.date)}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
title="测试"
|
||||
onPress={() => {
|
||||
setShow(!show)
|
||||
}}
|
||||
>
|
||||
</Button>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F5FCFF',
|
||||
marginTop: 128,
|
||||
padding: 16,
|
||||
padding: 20,
|
||||
},
|
||||
label: {
|
||||
marginTop: 20,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
result: {
|
||||
marginVertical: 5,
|
||||
fontSize: 14,
|
||||
color: '#333',
|
||||
},
|
||||
})
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { MoneyCategoryResult } from '@/request/api/money'
|
||||
import type { MoneyCategory } from '@/types/money'
|
||||
import type { MoneyCategory, MoneyRecord } from '@/types/money'
|
||||
import Big from 'big.js'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Keyboard, Pressable, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ActivityIndicator, Keyboard, Pressable, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { AInput, LucideIcon } from '@/components/common/ao'
|
||||
import { ADatePicker, ADateTimeDrawer, AInput, LucideIcon } from '@/components/common/ao'
|
||||
import SegmentedTabs from '@/components/common/ao/SegmentedTabs'
|
||||
import {
|
||||
Drawer,
|
||||
@@ -15,7 +15,9 @@ import {
|
||||
} from '@/components/ui/drawer'
|
||||
import { Grid, GridItem } from '@/components/ui/grid'
|
||||
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
|
||||
import { getMoneyCategoryApi } from '@/request/api/money'
|
||||
import { getMoneyCategoryApi, insertMoneyRecordApi } from '@/request/api/money'
|
||||
import { getCurrentDate } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
||||
|
||||
interface AddMoneyDrawerProps {
|
||||
@@ -27,6 +29,7 @@ interface KeydownItem {
|
||||
key: string | number
|
||||
content: React.ReactNode
|
||||
}
|
||||
|
||||
const keys: KeydownItem[] = [
|
||||
{ key: '1', content: <Text className="text-2xl">1</Text> },
|
||||
{ key: '2', content: <Text className="text-2xl">2</Text> },
|
||||
@@ -40,9 +43,9 @@ const keys: KeydownItem[] = [
|
||||
{ key: '8', content: <Text className="text-2xl">8</Text> },
|
||||
{ key: '9', content: <Text className="text-2xl">9</Text> },
|
||||
{ key: '-', content: <LucideIcon icon="Minus" size={22} strokeWidth={1.5} clickable={false} /> },
|
||||
{ key: 'repeat', content: (
|
||||
{ key: 'clear', content: (
|
||||
<View className="w-full h-full flex items-center justify-center">
|
||||
<Text className="text-xl text-gray-600">再记</Text>
|
||||
<Text className="text-xl text-gray-600">重置</Text>
|
||||
</View>
|
||||
) },
|
||||
{ key: '0', content: <Text className="text-2xl">0</Text> },
|
||||
@@ -59,21 +62,42 @@ export default function AddMoneyDrawer({
|
||||
onClose,
|
||||
}: AddMoneyDrawerProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
// 选择顺序:0 支出 1收入
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null)
|
||||
// 自定义键盘
|
||||
const [showKeydown, setShowKeydown] = useState(false)
|
||||
const [keydownInput, setKeydownInput] = useState<string>('')
|
||||
const [result, setResult] = useState('0')
|
||||
|
||||
// 自定义键盘计算过程
|
||||
const [keydownInput, setKeydownInput] = useState('')
|
||||
// 日期选择
|
||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||
// 备注聚焦
|
||||
const [remarkIsFocused, setRemarkIsFocused] = useState(false)
|
||||
// 分类
|
||||
const [categories, setCategories] = useState<MoneyCategoryResult>({
|
||||
income: [],
|
||||
expense: [],
|
||||
})
|
||||
|
||||
const keyboardHeight = useKeyboardHeight()
|
||||
console.log(keyboardHeight)
|
||||
|
||||
const defaultMoneyRecord: MoneyRecord = {
|
||||
id: -1,
|
||||
category_id: -1,
|
||||
user_id: -1,
|
||||
amount: '0.00',
|
||||
remark: '',
|
||||
record_time: getCurrentDate(),
|
||||
create_time: '',
|
||||
}
|
||||
|
||||
const [moneyRecord, setMoneyRecord] = useState<MoneyRecord>(defaultMoneyRecord)
|
||||
|
||||
function selectCategory(item: MoneyCategory) {
|
||||
setSelectedCategoryId(item.id)
|
||||
setMoneyRecord({
|
||||
...moneyRecord,
|
||||
category_id: item.id,
|
||||
})
|
||||
|
||||
setShowKeydown(true)
|
||||
}
|
||||
|
||||
@@ -94,11 +118,23 @@ export default function AddMoneyDrawer({
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
// 关闭整个drawer处理
|
||||
function onMainDrawerClose() {
|
||||
setShowKeydown(false)
|
||||
onClose()
|
||||
|
||||
// 清空键盘记录
|
||||
setKeydownInput('')
|
||||
// 关闭默认键盘
|
||||
setShowKeydown(false)
|
||||
// 设置默认值
|
||||
setMoneyRecord(defaultMoneyRecord)
|
||||
// 选中支出
|
||||
setSelectedIndex(0)
|
||||
// 关闭日期
|
||||
setShowDatePicker(false)
|
||||
}
|
||||
|
||||
// 计算过程
|
||||
const handlePress = (key: string | number) => {
|
||||
setKeydownInput((prev) => {
|
||||
let newInput = prev
|
||||
@@ -106,8 +142,17 @@ export default function AddMoneyDrawer({
|
||||
if (key === 'del') {
|
||||
newInput = prev.slice(0, -1)
|
||||
}
|
||||
else if (key === 'clear') {
|
||||
setMoneyRecord({
|
||||
...moneyRecord,
|
||||
amount: '0.00',
|
||||
remark: '',
|
||||
record_time: getCurrentDate(),
|
||||
})
|
||||
return ''
|
||||
}
|
||||
else if (key === '/') {
|
||||
// 完成操作,可以在这里提交或关闭键盘
|
||||
saveRecord()
|
||||
return prev
|
||||
}
|
||||
else {
|
||||
@@ -115,6 +160,11 @@ export default function AddMoneyDrawer({
|
||||
|
||||
// 检查是否要输入运算符(+ 或 -)
|
||||
if (keyStr === '+' || keyStr === '-') {
|
||||
// 如果当前输入为空,不允许以运算符开头
|
||||
if (prev === '') {
|
||||
return prev
|
||||
}
|
||||
|
||||
// 获取最后一个字符
|
||||
const lastChar = prev.slice(-1)
|
||||
|
||||
@@ -122,30 +172,43 @@ export default function AddMoneyDrawer({
|
||||
if (lastChar === '+' || lastChar === '-') {
|
||||
newInput = prev.slice(0, -1) + keyStr
|
||||
}
|
||||
// 如果最后一个字符是小数点,不允许在小数点后直接加运算符
|
||||
else if (lastChar === '.') {
|
||||
return prev
|
||||
}
|
||||
else {
|
||||
newInput = prev + keyStr
|
||||
}
|
||||
}
|
||||
// 检查是否要输入小数点(.)
|
||||
else if (keyStr === '.') {
|
||||
// 检查当前数字是否已经包含小数点
|
||||
const parts = prev.split(/[+-]/)
|
||||
const currentNumber = parts[parts.length - 1]
|
||||
// 如果当前输入为空,不允许以小数点开头,改为输入"0."
|
||||
if (prev === '') {
|
||||
newInput = '0.'
|
||||
}
|
||||
else {
|
||||
// 获取最后一个字符
|
||||
const lastChar = prev.slice(-1)
|
||||
|
||||
// 如果当前数字中还没有小数点,可以添加
|
||||
if (!currentNumber.includes('.')) {
|
||||
// 检查当前数字的小数位数(应该是0,因为还没有小数点)
|
||||
if (currentNumber === '' || /[+-]/.test(prev.slice(-1))) {
|
||||
// 如果最后一个字符是运算符,则需要在运算符后添加"0."而不是单独的"."
|
||||
if (lastChar === '+' || lastChar === '-') {
|
||||
newInput = `${prev}0.`
|
||||
}
|
||||
else {
|
||||
newInput = `${prev}.`
|
||||
// 检查当前数字是否已经包含小数点
|
||||
const parts = prev.split(/[+-]/)
|
||||
const currentNumber = parts[parts.length - 1]
|
||||
|
||||
// 如果当前数字中还没有小数点,可以添加
|
||||
if (!currentNumber.includes('.')) {
|
||||
newInput = `${prev}.`
|
||||
}
|
||||
else {
|
||||
// 已经有小数点了,不重复添加
|
||||
newInput = prev
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 已经有小数点了,不重复添加
|
||||
newInput = prev
|
||||
}
|
||||
}
|
||||
// 处理数字输入
|
||||
else if (/^\d$/.test(keyStr)) {
|
||||
@@ -178,10 +241,16 @@ export default function AddMoneyDrawer({
|
||||
// 计算结果
|
||||
try {
|
||||
const computed = calculateWithBig(newInput)
|
||||
setResult(computed)
|
||||
setMoneyRecord({
|
||||
...moneyRecord,
|
||||
amount: computed,
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
setResult('0')
|
||||
setMoneyRecord({
|
||||
...moneyRecord,
|
||||
amount: '0',
|
||||
})
|
||||
}
|
||||
|
||||
return newInput
|
||||
@@ -242,166 +311,213 @@ export default function AddMoneyDrawer({
|
||||
return total.toFixed(2)
|
||||
}
|
||||
}
|
||||
const inputRef = useRef<any>(null)
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
// 保存
|
||||
function saveRecord() {
|
||||
if (moneyRecord.category_id === -1) {
|
||||
toast.show({
|
||||
title: '请选择分类',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (moneyRecord.amount === '0.00') {
|
||||
toast.show({
|
||||
title: '请输入金额',
|
||||
})
|
||||
}
|
||||
|
||||
insertMoneyRecordApi({
|
||||
...moneyRecord,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
onMainDrawerClose()
|
||||
}
|
||||
else {
|
||||
toast.show({
|
||||
title: res.message || '保存失败',
|
||||
})
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
size="full"
|
||||
anchor="bottom"
|
||||
onClose={onMainDrawerClose}
|
||||
>
|
||||
<DrawerBackdrop />
|
||||
|
||||
<DrawerContent
|
||||
style={{
|
||||
padding: 0,
|
||||
flex: 1,
|
||||
|
||||
}}
|
||||
<>
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
size="full"
|
||||
anchor="bottom"
|
||||
onClose={onMainDrawerClose}
|
||||
>
|
||||
<DrawerHeader
|
||||
className="flex items-center justify-between"
|
||||
style={{ paddingTop: insets.top + 8, paddingLeft: 16, paddingRight: 16 }}
|
||||
>
|
||||
<View className="w-1" />
|
||||
<SegmentedTabs
|
||||
value={selectedIndex}
|
||||
tabs={[{ label: '收入' }, { label: '支出' }]}
|
||||
onChange={setSelectedIndex}
|
||||
style={{ width: 180 }}
|
||||
className="mx-auto"
|
||||
/>
|
||||
<Pressable onPress={onMainDrawerClose}>
|
||||
<Text className="text-gray-500 font-medium">取消</Text>
|
||||
</Pressable>
|
||||
</DrawerHeader>
|
||||
<DrawerBody style={{ flex: 1, paddingLeft: 16, paddingRight: 16, marginBottom: 0, backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Grid className="gap-4 mb-8" _extra={{ className: 'grid-cols-4' }}>
|
||||
{(() => {
|
||||
const list = selectedIndex === 0 ? categories.income : categories.expense
|
||||
const items = list.map((item) => {
|
||||
const isSelected = selectedCategoryId === item.id
|
||||
return (
|
||||
<GridItem key={item.id} _extra={{ className: 'col-span-1' }}>
|
||||
<Pressable
|
||||
className="flex flex-col items-center justify-center rounded"
|
||||
onPress={() => selectCategory(item)}
|
||||
>
|
||||
<View
|
||||
className={`w-14 h-14 rounded-full flex items-center justify-center ${
|
||||
isSelected ? 'bg-blue-100' : 'bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<MoneyCategoryIcon name={item.icon} size={28} />
|
||||
</View>
|
||||
<Text className="text-sm mt-1">
|
||||
{item.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</GridItem>
|
||||
)
|
||||
})
|
||||
<DrawerBackdrop />
|
||||
|
||||
const remainder = list.length % 4
|
||||
if (remainder > 0) {
|
||||
const emptyCount = 4 - remainder
|
||||
for (let i = 0; i < emptyCount; i++) {
|
||||
items.push(<GridItem key={`empty-${i}`} _extra={{ className: 'col-span-1' }} />)
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
})()}
|
||||
</Grid>
|
||||
</DrawerBody>
|
||||
|
||||
{showKeydown && (
|
||||
<View style={{
|
||||
backgroundColor: '#fff',
|
||||
elevation: 20,
|
||||
}}
|
||||
<DrawerContent className="p-0 flex-1">
|
||||
<DrawerHeader
|
||||
className="flex items-center justify-between"
|
||||
style={{ paddingTop: insets.top + 8, paddingLeft: 16, paddingRight: 16 }}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View className="h-28 flex pl-4 pr-4 justify-center">
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<Pressable
|
||||
className="flex flex-row items-center gap-1.5 px-2 py-1.5 rounded-md bg-gray-50 active:opacity-70"
|
||||
>
|
||||
<LucideIcon icon="Calendar" size={16} color="#60a5fa" />
|
||||
<Text className="text-gray-600 text-sm">2024/11/05</Text>
|
||||
<LucideIcon icon="ChevronDown" size={12} color="#aaa" />
|
||||
</Pressable>
|
||||
<Text className="text-3xl text-blue-700 text-right">{result}</Text>
|
||||
<View className="w-1" />
|
||||
<SegmentedTabs
|
||||
value={selectedIndex}
|
||||
tabs={[{ label: '支出' }, { label: '收入' }]}
|
||||
onChange={setSelectedIndex}
|
||||
style={{ width: 180 }}
|
||||
className="mx-auto"
|
||||
/>
|
||||
<Pressable onPress={onMainDrawerClose}>
|
||||
<Text className="text-gray-500 font-medium">取消</Text>
|
||||
</Pressable>
|
||||
</DrawerHeader>
|
||||
<DrawerBody
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
marginBottom: 0,
|
||||
backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Grid className="gap-4 mb-8" _extra={{ className: 'grid-cols-4' }}>
|
||||
{(() => {
|
||||
const list = selectedIndex === 1 ? categories.income : categories.expense
|
||||
const items = list.map((item) => {
|
||||
const isSelected = moneyRecord.category_id === item.id
|
||||
return (
|
||||
<GridItem key={item.id} _extra={{ className: 'col-span-1' }}>
|
||||
<Pressable
|
||||
className="flex flex-col items-center justify-center rounded"
|
||||
onPress={() => selectCategory(item)}
|
||||
>
|
||||
<View
|
||||
className={`w-14 h-14 rounded-full flex items-center justify-center ${
|
||||
isSelected ? 'bg-blue-100' : 'bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<MoneyCategoryIcon name={item.icon} size={28} />
|
||||
</View>
|
||||
<Text className="text-sm mt-1">
|
||||
{item.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</GridItem>
|
||||
)
|
||||
})
|
||||
|
||||
const remainder = list.length % 4
|
||||
if (remainder > 0) {
|
||||
const emptyCount = 4 - remainder
|
||||
for (let i = 0; i < emptyCount; i++) {
|
||||
items.push(<GridItem key={`empty-${i}`} _extra={{ className: 'col-span-1' }} />)
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
})()}
|
||||
</Grid>
|
||||
</DrawerBody>
|
||||
|
||||
{showKeydown && (
|
||||
<View style={{
|
||||
backgroundColor: '#fff',
|
||||
elevation: 20,
|
||||
}}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View className="h-28 flex pl-4 pr-4 justify-center">
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<Pressable
|
||||
className="flex flex-row items-center gap-1.5 px-2 py-1.5 rounded-md bg-gray-50 active:opacity-70"
|
||||
onPress={() => setShowDatePicker(true)}
|
||||
>
|
||||
<LucideIcon icon="Calendar" size={16} color="#60a5fa" clickable={false} />
|
||||
<Text className="text-gray-600 text-sm">{moneyRecord.record_time}</Text>
|
||||
<LucideIcon icon="ChevronDown" size={12} color="#aaa" clickable={false} />
|
||||
</Pressable>
|
||||
<Text className="text-3xl text-blue-700 text-right">{moneyRecord.amount}</Text>
|
||||
</View>
|
||||
|
||||
<Text className="text-gray-400 text-right">{keydownInput}</Text>
|
||||
<AInput
|
||||
value={moneyRecord.remark}
|
||||
placeholder="备注"
|
||||
className="bg-[#f2f3f5] border-0"
|
||||
size="sm"
|
||||
onChangeText={(val) => {
|
||||
setMoneyRecord({
|
||||
...moneyRecord,
|
||||
remark: val,
|
||||
})
|
||||
}}
|
||||
onFocus={() => setRemarkIsFocused(true)}
|
||||
onBlur={() => setRemarkIsFocused(false)}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<View className="flex flex-row flex-wrap" style={{ height: 220 }}>
|
||||
{keys.map((item, idx) => {
|
||||
const isFirstCol = idx % 4 === 0
|
||||
const isFirstRow = idx < 4
|
||||
const isLastCol = (idx + 1) % 4 === 0
|
||||
const isLastRow = idx >= 12
|
||||
|
||||
<Text className="text-gray-400 text-right">{keydownInput}</Text>
|
||||
<AInput
|
||||
ref={inputRef}
|
||||
value={keydownInput}
|
||||
placeholder="备注"
|
||||
className="bg-[#f2f3f5] border-0"
|
||||
size="sm"
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
/>
|
||||
return (
|
||||
<Pressable
|
||||
key={item.key}
|
||||
onPress={() => handlePress(item.key)}
|
||||
style={{
|
||||
width: '25%',
|
||||
height: '25%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: isFirstRow ? 0.3 : 0,
|
||||
borderLeftWidth: isFirstCol ? 0.3 : 0,
|
||||
borderRightWidth: !isLastCol ? 0.3 : 0,
|
||||
borderBottomWidth: !isLastRow ? 0.3 : 0,
|
||||
borderColor: '#ddd',
|
||||
}}
|
||||
>
|
||||
{({ pressed }) => {
|
||||
const defaultBg = item.key === '/' ? '#e6d7b8' : '#fff'
|
||||
const pressedBg = item.key === '/' ? '#f5e9d4' : '#f2f2f2'
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: pressed ? pressedBg : defaultBg,
|
||||
}}
|
||||
/>
|
||||
{item.content}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<View className="flex flex-row flex-wrap" style={{ height: 220 }}>
|
||||
{keys.map((item, idx) => {
|
||||
const isFirstCol = idx % 4 === 0
|
||||
const isFirstRow = idx < 4
|
||||
const isLastCol = (idx + 1) % 4 === 0
|
||||
const isLastRow = idx >= 12
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={item.key}
|
||||
onPress={() => handlePress(item.key)}
|
||||
style={{
|
||||
width: '25%',
|
||||
height: '25%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: isFirstRow ? 0.3 : 0,
|
||||
borderLeftWidth: isFirstCol ? 0.3 : 0,
|
||||
borderRightWidth: !isLastCol ? 0.3 : 0,
|
||||
borderBottomWidth: !isLastRow ? 0.3 : 0,
|
||||
borderColor: '#ddd',
|
||||
}}
|
||||
>
|
||||
{({ pressed }) => {
|
||||
const defaultBg = item.key === '/' ? '#e6d7b8' : '#fff'
|
||||
const pressedBg = item.key === '/' ? '#f5e9d4' : '#f2f2f2'
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: pressed ? pressedBg : defaultBg,
|
||||
}}
|
||||
/>
|
||||
{item.content}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
{remarkIsFocused && keyboardHeight > 220 && (
|
||||
<View style={{ height: keyboardHeight - 220 }} />
|
||||
)}
|
||||
</View>
|
||||
{isFocused && keyboardHeight > 220 && (
|
||||
<View style={{ height: keyboardHeight - 220 }} />
|
||||
)}
|
||||
|
||||
</View>
|
||||
)}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<ADateTimeDrawer
|
||||
isOpen={showDatePicker}
|
||||
defaultDate={moneyRecord.record_time}
|
||||
onConfirm={(val) => {
|
||||
setMoneyRecord({
|
||||
...moneyRecord,
|
||||
record_time: val,
|
||||
})
|
||||
setShowDatePicker(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowDatePicker((false))
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import type { LayoutChangeEvent } from 'react-native'
|
||||
import { BlurView } from 'expo-blur'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { ScrollView, Text, View } from 'react-native'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { ScrollView, Text, TouchableOpacity, View } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { LucideIcon } from '@/components/common/ao'
|
||||
import { ADateTimeDrawer, LucideIcon } from '@/components/common/ao'
|
||||
import { IconJiaotong } from '@/components/iconfont'
|
||||
import { Fab, FabIcon } from '@/components/ui/fab'
|
||||
import { Grid, GridItem } from '@/components/ui/grid'
|
||||
import { AddIcon } from '@/components/ui/icon'
|
||||
import { Progress, ProgressFilledTrack } from '@/components/ui/progress'
|
||||
import { queryMoneyRecordsApi } from '@/request/api/money'
|
||||
import { getCurrentYearMonth } from '@/utils/time'
|
||||
import AddMoneyDrawer from './components/AddDrawer'
|
||||
import TransactionsList from './components/TransactionsList'
|
||||
|
||||
export default function Money() {
|
||||
const [showDrawer, setShowDrawer] = useState(false)
|
||||
const [showAddDrawer, setShowAddDrawer] = useState(false)
|
||||
|
||||
// 安全高度
|
||||
const insets = useSafeAreaInsets()
|
||||
const [gradientHeight, setGradientHeight] = useState(200)
|
||||
const gridLayout = useRef<{ y: number, height: number } | null>(null)
|
||||
|
||||
const onGridLayout = (e: LayoutChangeEvent) => {
|
||||
// 仅首次布局计算高度
|
||||
if (!gridLayout.current) {
|
||||
const { y, height } = e.nativeEvent.layout
|
||||
gridLayout.current = { y, height }
|
||||
@@ -28,6 +31,20 @@ export default function Money() {
|
||||
}
|
||||
}
|
||||
|
||||
// 日期筛选
|
||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
record_time: getCurrentYearMonth(),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
queryMoneyRecordsApi({
|
||||
record_time: searchParams.record_time,
|
||||
}).then((res) => {
|
||||
console.log(res.data)
|
||||
})
|
||||
}, [searchParams])
|
||||
|
||||
return (
|
||||
<View
|
||||
className="flex-1 relative bg-white"
|
||||
@@ -52,10 +69,16 @@ export default function Money() {
|
||||
|
||||
<View style={{ zIndex: 10, paddingLeft: 12, paddingRight: 12 }}>
|
||||
<View className="flex flex-row justify-between items-center py-2">
|
||||
<View className="flex flex-row items-center gap-2">
|
||||
<Text className="font-bold text-base">2025/11/10</Text>
|
||||
<LucideIcon icon="CircleChevronDown" size={14} />
|
||||
</View>
|
||||
<BlurView intensity={80} className="px-2 py-1">
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowDatePicker(true)}
|
||||
className="flex flex-row items-center gap-2"
|
||||
>
|
||||
<Text className="font-bold text-base">{searchParams.record_time}</Text>
|
||||
<LucideIcon icon="ArrowBigDownDash" size={16} />
|
||||
</TouchableOpacity>
|
||||
</BlurView>
|
||||
|
||||
<View className="flex flex-row items-center gap-2">
|
||||
<LucideIcon icon="SlidersHorizontal" />
|
||||
</View>
|
||||
@@ -144,16 +167,31 @@ export default function Money() {
|
||||
<Fab
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
setShowDrawer(true)
|
||||
setShowAddDrawer(true)
|
||||
}}
|
||||
>
|
||||
<FabIcon as={AddIcon} size="xl" />
|
||||
</Fab>
|
||||
|
||||
<AddMoneyDrawer
|
||||
isOpen={showDrawer}
|
||||
isOpen={showAddDrawer}
|
||||
onClose={() => {
|
||||
setShowDrawer(false)
|
||||
setShowAddDrawer(false)
|
||||
}}
|
||||
/>
|
||||
|
||||
<ADateTimeDrawer
|
||||
isOpen={showDatePicker}
|
||||
modeType="year-month"
|
||||
onConfirm={(val) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
record_time: val,
|
||||
})
|
||||
setShowDatePicker(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowDatePicker(false)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { DateTimePickerProps } from 'react-native-modal-datetime-picker'
|
||||
import dayjs from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import React, { useMemo } from 'react'
|
||||
import DateTimePickerModal from 'react-native-modal-datetime-picker'
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
interface ADatePickerProps extends Omit<DateTimePickerProps, 'onConfirm' | 'onCancel' | 'date'> {
|
||||
format?: string
|
||||
onConfirm?: (value: string) => void
|
||||
date?: Date | string
|
||||
is24Hour?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
export default function DatePicker({
|
||||
mode = 'date',
|
||||
format,
|
||||
date,
|
||||
onConfirm,
|
||||
is24Hour = true,
|
||||
onCancel,
|
||||
...restProps
|
||||
}: ADatePickerProps) {
|
||||
const parsedDate = useMemo(() => {
|
||||
if (!date)
|
||||
return new Date()
|
||||
|
||||
if (typeof date === 'string') {
|
||||
// 支持多种常见日期格式
|
||||
const formats = ['YYYY-MM-DD', 'YYYY/MM/DD', 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY/MM/DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss']
|
||||
for (const fmt of formats) {
|
||||
const d = dayjs(date, fmt, true)
|
||||
if (d.isValid())
|
||||
return d.toDate()
|
||||
}
|
||||
// fallback: 尝试直接解析
|
||||
const d = dayjs(date)
|
||||
if (d.isValid())
|
||||
return d.toDate()
|
||||
// 仍然不合法就返回当前时间
|
||||
return new Date()
|
||||
}
|
||||
|
||||
return date
|
||||
}, [date])
|
||||
|
||||
const handleConfirm = (selectedDate: Date) => {
|
||||
const fmt = format || (() => {
|
||||
switch (mode) {
|
||||
case 'time':
|
||||
return 'HH:mm:00'
|
||||
case 'datetime':
|
||||
return 'YYYY-MM-DD HH:mm:00'
|
||||
case 'date':
|
||||
default:
|
||||
return 'YYYY-MM-DD'
|
||||
}
|
||||
})()
|
||||
|
||||
const localTimeStr = dayjs(selectedDate).format(fmt)
|
||||
onConfirm?.(localTimeStr)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<DateTimePickerModal
|
||||
{...restProps}
|
||||
mode={mode}
|
||||
date={parsedDate}
|
||||
locale="zh-CN"
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
is24Hour={is24Hour}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { DateType, OrderedDateParts } from 'react-native-dates-picker/lib/typescript/types'
|
||||
import dayjs from 'dayjs'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import DateTimePicker from 'react-native-dates-picker'
|
||||
|
||||
import { Drawer, DrawerBackdrop, DrawerContent, DrawerFooter, DrawerHeader } from '@/components/ui/drawer'
|
||||
import AButton from './Button'
|
||||
|
||||
type DateWheelMode
|
||||
= | 'year-month'
|
||||
| 'year-month-day'
|
||||
| 'hour-minute-second'
|
||||
| 'year-month-day-hour-minute-second'
|
||||
|
||||
interface DateDrawerProps {
|
||||
isOpen?: boolean
|
||||
defaultDate?: DateType
|
||||
modeType?: DateWheelMode
|
||||
onClose?: () => void
|
||||
onConfirm?: (dateStr: string) => void
|
||||
}
|
||||
|
||||
const modeColumnsMap: Record<DateWheelMode, OrderedDateParts[]> = {
|
||||
'year-month': ['year', 'month'],
|
||||
'year-month-day': ['year', 'month', 'day'],
|
||||
'hour-minute-second': ['hour', 'minute', 'second'],
|
||||
'year-month-day-hour-minute-second': ['year', 'month', 'day', 'hour', 'minute', 'second'],
|
||||
}
|
||||
|
||||
const modeFormatMap: Record<DateWheelMode, string> = {
|
||||
'year-month': 'YYYY-MM',
|
||||
'year-month-day': 'YYYY-MM-DD',
|
||||
'hour-minute-second': 'HH:mm:ss',
|
||||
'year-month-day-hour-minute-second': 'YYYY-MM-DD HH:mm:ss',
|
||||
}
|
||||
|
||||
export default function DateWheelDrawer({
|
||||
isOpen,
|
||||
defaultDate = dayjs(),
|
||||
onClose,
|
||||
onConfirm,
|
||||
modeType = 'year-month-day',
|
||||
}: DateDrawerProps) {
|
||||
const columns = modeColumnsMap[modeType]
|
||||
const format = modeFormatMap[modeType]
|
||||
|
||||
// 当前选择的值
|
||||
const [date, setDate] = useState<DateType>(defaultDate)
|
||||
// 保存上一次确认的值
|
||||
const lastConfirmedRef = useRef<DateType>(defaultDate)
|
||||
|
||||
// 每次弹窗打开时,把滚轮显示初始化为上一次确认的值
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setDate(lastConfirmedRef.current)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
// 点击取消
|
||||
const handleCancel = () => {
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
// 点击确认
|
||||
const handleConfirm = () => {
|
||||
console.log("确实")
|
||||
onClose?.()
|
||||
lastConfirmedRef.current = date // 保存为上一次确认的值
|
||||
|
||||
if (onConfirm) {
|
||||
const formatted = dayjs(date).format(format)
|
||||
onConfirm(formatted)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
anchor="bottom"
|
||||
closeOnOverlayClick
|
||||
onClose={onClose}
|
||||
size="md"
|
||||
>
|
||||
<DrawerBackdrop />
|
||||
<DrawerContent className="p-0 flex-1">
|
||||
<DrawerHeader className="flex items-center justify-between p-4">
|
||||
<AButton action="secondary" onPress={handleCancel}>取消</AButton>
|
||||
<AButton onPress={handleConfirm}>确认</AButton>
|
||||
</DrawerHeader>
|
||||
|
||||
<View className="flex-1 flex items-center justify-center">
|
||||
<DateTimePicker
|
||||
mode="wheel"
|
||||
date={date}
|
||||
columns={columns}
|
||||
wheelPickerSelectedIndicatorStyle={{ backgroundColor: '#eee' }}
|
||||
onChange={params => setDate(params.date)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<DrawerFooter></DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import Button from './Button'
|
||||
import Checkbox from './Checkbox'
|
||||
import DatePicker from './DatePicker'
|
||||
import DateTimeDrawer from './DateTimeDrawer'
|
||||
import Form, { FormRef } from './Form'
|
||||
import FormItem from './FormItem'
|
||||
import Input from './Input'
|
||||
@@ -8,6 +10,8 @@ import LucideIcon from './LucideIcon'
|
||||
export {
|
||||
Button as AButton,
|
||||
Checkbox as ACheckbox,
|
||||
DatePicker as ADatePicker,
|
||||
DateTimeDrawer as ADateTimeDrawer,
|
||||
Form as AForm,
|
||||
FormItem as AFormItem,
|
||||
FormRef as AFormRef,
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import type { MoneyCategory } from '@/types/money'
|
||||
import type { MoneyCategory, MoneyRecord } from '@/types/money'
|
||||
import alovaInstance from '../index'
|
||||
|
||||
const API_PREFIX = '/money'
|
||||
|
||||
export interface QueryMoneyRecordsParams {
|
||||
record_time: string
|
||||
}
|
||||
|
||||
export interface QueryMoneyRecordsResult {
|
||||
[key: string]: MoneyRecord[]
|
||||
}
|
||||
export function queryMoneyRecordsApi(params: QueryMoneyRecordsParams) {
|
||||
return alovaInstance.Post<ResponseData<QueryMoneyRecordsResult>>(`${API_PREFIX}/query`, params)
|
||||
}
|
||||
|
||||
export interface MoneyCategoryResult {
|
||||
income: MoneyCategory[]
|
||||
expense: MoneyCategory[]
|
||||
|
||||
@@ -30,8 +30,8 @@ function isWhiteListed(url: string) {
|
||||
}
|
||||
|
||||
const alovaInstance = createAlova({
|
||||
baseURL: 'http://192.168.5.6:8085/app',
|
||||
// baseURL: 'http://192.168.1.136:8085/app',
|
||||
// baseURL: 'http://192.168.5.6:8085/app',
|
||||
baseURL: 'http://192.168.1.136:8085/app',
|
||||
requestAdapter: axiosRequestAdapter(),
|
||||
timeout: 5000,
|
||||
cacheFor: null,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
/**
|
||||
* 获取当前日期,默认格式: YYYY-MM-DD
|
||||
*/
|
||||
export function getCurrentDate(format = 'YYYY-MM-DD'): string {
|
||||
return dayjs().format(format)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期时间,默认格式: YYYY-MM-DD HH:mm:ss
|
||||
*/
|
||||
export function getCurrentDateTime(format = 'YYYY-MM-DD HH:mm:ss'): string {
|
||||
return dayjs().format(format)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间,默认格式: HH:mm:ss
|
||||
*/
|
||||
export function getCurrentTime(format = 'HH:mm:ss'): string {
|
||||
return dayjs().format(format)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前年月,默认格式: YYYY-MM
|
||||
*/
|
||||
export function getCurrentYearMonth(format = 'YYYY-MM'): string {
|
||||
return dayjs().format(format)
|
||||
}
|
||||
Reference in new issue
Block a user