u
This commit is contained in:
1 parent
8a6d29f5d0
commit
7ed98a04c1
11 files changed
+1127
-55
No files matched your search
@@ -5,5 +5,16 @@ module.exports = function (api) {
|
|||||||
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
||||||
'nativewind/babel',
|
'nativewind/babel',
|
||||||
],
|
],
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
'module-resolver',
|
||||||
|
{
|
||||||
|
root: ['./src'],
|
||||||
|
alias: {
|
||||||
|
'@': './src',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"@gluestack/ui-next-adapter": "^3.0.3",
|
"@gluestack/ui-next-adapter": "^3.0.3",
|
||||||
"@legendapp/motion": "^2.4.0",
|
"@legendapp/motion": "^2.4.0",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
|
"@react-native-segmented-control/segmented-control": "2.5.7",
|
||||||
"alova": "^3.3.4",
|
"alova": "^3.3.4",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"expo": "~54.0.20",
|
"expo": "~54.0.20",
|
||||||
@@ -35,12 +36,14 @@
|
|||||||
"react-native-reanimated": "~4.1.3",
|
"react-native-reanimated": "~4.1.3",
|
||||||
"react-native-safe-area-context": "~5.6.1",
|
"react-native-safe-area-context": "~5.6.1",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
|
"react-native-segmented-control-2": "^2.1.1",
|
||||||
"react-native-svg": "15.12.0",
|
"react-native-svg": "15.12.0",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^6.2.0",
|
"@antfu/eslint-config": "^6.2.0",
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
|
"babel-plugin-module-resolver": "^5.0.2",
|
||||||
"eslint": "^9.38.0",
|
"eslint": "^9.38.0",
|
||||||
"react-native-iconfont-cli": "^2.2.4",
|
"react-native-iconfont-cli": "^2.2.4",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
|
|||||||
+118
-8
@@ -26,6 +26,9 @@ importers:
|
|||||||
'@react-native-async-storage/async-storage':
|
'@react-native-async-storage/async-storage':
|
||||||
specifier: 2.2.0
|
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))
|
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-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)
|
||||||
alova:
|
alova:
|
||||||
specifier: ^3.3.4
|
specifier: ^3.3.4
|
||||||
version: 3.3.4
|
version: 3.3.4
|
||||||
@@ -83,6 +86,9 @@ importers:
|
|||||||
react-native-screens:
|
react-native-screens:
|
||||||
specifier: ~4.16.0
|
specifier: ~4.16.0
|
||||||
version: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 4.16.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-segmented-control-2:
|
||||||
|
specifier: ^2.1.1
|
||||||
|
version: 2.1.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-svg:
|
react-native-svg:
|
||||||
specifier: 15.12.0
|
specifier: 15.12.0
|
||||||
version: 15.12.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 15.12.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -96,6 +102,9 @@ importers:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ~19.1.0
|
specifier: ~19.1.0
|
||||||
version: 19.1.17
|
version: 19.1.17
|
||||||
|
babel-plugin-module-resolver:
|
||||||
|
specifier: ^5.0.2
|
||||||
|
version: 5.0.2
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.38.0
|
specifier: ^9.38.0
|
||||||
version: 9.39.0(jiti@1.21.7)
|
version: 9.39.0(jiti@1.21.7)
|
||||||
@@ -1571,6 +1580,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react-native: ^0.0.0-0 || >=0.65 <1.0
|
react-native: ^0.0.0-0 || >=0.65 <1.0
|
||||||
|
|
||||||
|
'@react-native-segmented-control/segmented-control@2.5.7':
|
||||||
|
resolution: {integrity: sha512-l84YeVX8xAU3lvOJSvV4nK/NbGhIm2gBfveYolwaoCbRp+/SLXtc6mYrQmM9ScXNwU14mnzjQTpTHWl5YPnkzQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.0'
|
||||||
|
react-native: '>=0.62'
|
||||||
|
|
||||||
'@react-native/assets-registry@0.81.5':
|
'@react-native/assets-registry@0.81.5':
|
||||||
resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==}
|
resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==}
|
||||||
engines: {node: '>= 20.19.4'}
|
engines: {node: '>= 20.19.4'}
|
||||||
@@ -2257,6 +2272,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
|
resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
|
||||||
|
babel-plugin-module-resolver@5.0.2:
|
||||||
|
resolution: {integrity: sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==}
|
||||||
|
|
||||||
babel-plugin-polyfill-corejs2@0.4.14:
|
babel-plugin-polyfill-corejs2@0.4.14:
|
||||||
resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==}
|
resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3198,10 +3216,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
find-babel-config@2.1.2:
|
||||||
|
resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==}
|
||||||
|
|
||||||
find-up-simple@1.0.1:
|
find-up-simple@1.0.1:
|
||||||
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
|
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
find-up@3.0.0:
|
||||||
|
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3324,6 +3349,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
deprecated: Glob versions prior to v9 are no longer supported
|
||||||
|
|
||||||
|
glob@9.3.5:
|
||||||
|
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
global-dirs@0.1.1:
|
global-dirs@0.1.1:
|
||||||
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
|
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -3690,56 +3719,48 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-arm64-gnu@1.30.2:
|
lightningcss-linux-arm64-gnu@1.30.2:
|
||||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.27.0:
|
lightningcss-linux-arm64-musl@1.27.0:
|
||||||
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.30.2:
|
lightningcss-linux-arm64-musl@1.30.2:
|
||||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.27.0:
|
lightningcss-linux-x64-gnu@1.27.0:
|
||||||
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.30.2:
|
lightningcss-linux-x64-gnu@1.30.2:
|
||||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.27.0:
|
lightningcss-linux-x64-musl@1.27.0:
|
||||||
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.30.2:
|
lightningcss-linux-x64-musl@1.30.2:
|
||||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.27.0:
|
lightningcss-win32-arm64-msvc@1.27.0:
|
||||||
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
||||||
@@ -3784,6 +3805,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
locate-path@3.0.0:
|
||||||
|
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4131,6 +4156,10 @@ packages:
|
|||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||||
|
|
||||||
|
minimatch@8.0.4:
|
||||||
|
resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
minimatch@9.0.5:
|
minimatch@9.0.5:
|
||||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
@@ -4138,6 +4167,10 @@ packages:
|
|||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
|
minipass@4.2.8:
|
||||||
|
resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
minipass@7.1.2:
|
minipass@7.1.2:
|
||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
@@ -4291,6 +4324,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-locate@3.0.0:
|
||||||
|
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
p-locate@4.1.0:
|
p-locate@4.1.0:
|
||||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4331,6 +4368,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
path-exists@3.0.0:
|
||||||
|
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
path-exists@4.0.0:
|
path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4382,6 +4423,10 @@ packages:
|
|||||||
pkg-types@2.3.0:
|
pkg-types@2.3.0:
|
||||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||||
|
|
||||||
|
pkg-up@3.1.0:
|
||||||
|
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
plist@3.1.0:
|
plist@3.1.0:
|
||||||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||||
engines: {node: '>=10.4.0'}
|
engines: {node: '>=10.4.0'}
|
||||||
@@ -4601,6 +4646,12 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
|
react-native-segmented-control-2@2.1.1:
|
||||||
|
resolution: {integrity: sha512-qtDM4KhPMb/V3R6vVGWkhPVmUNLu+Vt+ypuYbsErFTCXLKMTmYfT+xH1rtvXn9cfzBM+lzjTiI5ij3hgs9/Tug==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=17 || >=18'
|
||||||
|
react-native: '>=0.71'
|
||||||
|
|
||||||
react-native-svg@15.12.0:
|
react-native-svg@15.12.0:
|
||||||
resolution: {integrity: sha512-iE25PxIJ6V0C6krReLquVw6R0QTsRTmEQc4K2Co3P6zsimU/jltcDBKYDy1h/5j9S/fqmMeXnpM+9LEWKJKI6A==}
|
resolution: {integrity: sha512-iE25PxIJ6V0C6krReLquVw6R0QTsRTmEQc4K2Co3P6zsimU/jltcDBKYDy1h/5j9S/fqmMeXnpM+9LEWKJKI6A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4726,6 +4777,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
|
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
|
reselect@4.1.8:
|
||||||
|
resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==}
|
||||||
|
|
||||||
reserved-identifiers@1.2.0:
|
reserved-identifiers@1.2.0:
|
||||||
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
|
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -7517,6 +7571,11 @@ snapshots:
|
|||||||
merge-options: 3.0.4
|
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: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(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
|
||||||
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
|
||||||
'@react-native/assets-registry@0.81.5': {}
|
'@react-native/assets-registry@0.81.5': {}
|
||||||
|
|
||||||
'@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.5)':
|
'@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.5)':
|
||||||
@@ -8447,6 +8506,14 @@ snapshots:
|
|||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
'@types/babel__traverse': 7.28.0
|
'@types/babel__traverse': 7.28.0
|
||||||
|
|
||||||
|
babel-plugin-module-resolver@5.0.2:
|
||||||
|
dependencies:
|
||||||
|
find-babel-config: 2.1.2
|
||||||
|
glob: 9.3.5
|
||||||
|
pkg-up: 3.1.0
|
||||||
|
reselect: 4.1.8
|
||||||
|
resolve: 1.22.11
|
||||||
|
|
||||||
babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5):
|
babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/compat-data': 7.28.5
|
'@babel/compat-data': 7.28.5
|
||||||
@@ -9501,8 +9568,16 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
find-babel-config@2.1.2:
|
||||||
|
dependencies:
|
||||||
|
json5: 2.2.3
|
||||||
|
|
||||||
find-up-simple@1.0.1: {}
|
find-up-simple@1.0.1: {}
|
||||||
|
|
||||||
|
find-up@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
locate-path: 3.0.0
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 5.0.0
|
locate-path: 5.0.0
|
||||||
@@ -9628,6 +9703,13 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
|
|
||||||
|
glob@9.3.5:
|
||||||
|
dependencies:
|
||||||
|
fs.realpath: 1.0.0
|
||||||
|
minimatch: 8.0.4
|
||||||
|
minipass: 4.2.8
|
||||||
|
path-scurry: 1.11.1
|
||||||
|
|
||||||
global-dirs@0.1.1:
|
global-dirs@0.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ini: 1.3.8
|
ini: 1.3.8
|
||||||
@@ -10047,6 +10129,11 @@ snapshots:
|
|||||||
pkg-types: 2.3.0
|
pkg-types: 2.3.0
|
||||||
quansync: 0.2.11
|
quansync: 0.2.11
|
||||||
|
|
||||||
|
locate-path@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
p-locate: 3.0.0
|
||||||
|
path-exists: 3.0.0
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 4.1.0
|
p-locate: 4.1.0
|
||||||
@@ -10795,12 +10882,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.12
|
brace-expansion: 1.1.12
|
||||||
|
|
||||||
|
minimatch@8.0.4:
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimatch@9.0.5:
|
minimatch@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.2
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
|
minipass@4.2.8: {}
|
||||||
|
|
||||||
minipass@7.1.2: {}
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
minizlib@3.1.0:
|
minizlib@3.1.0:
|
||||||
@@ -10950,6 +11043,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
|
|
||||||
|
p-locate@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
p-limit: 2.3.0
|
||||||
|
|
||||||
p-locate@4.1.0:
|
p-locate@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 2.3.0
|
p-limit: 2.3.0
|
||||||
@@ -10982,6 +11079,8 @@ snapshots:
|
|||||||
|
|
||||||
parseurl@1.3.3: {}
|
parseurl@1.3.3: {}
|
||||||
|
|
||||||
|
path-exists@3.0.0: {}
|
||||||
|
|
||||||
path-exists@4.0.0: {}
|
path-exists@4.0.0: {}
|
||||||
|
|
||||||
path-is-absolute@1.0.1: {}
|
path-is-absolute@1.0.1: {}
|
||||||
@@ -11021,6 +11120,10 @@ snapshots:
|
|||||||
exsolve: 1.0.7
|
exsolve: 1.0.7
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
pkg-up@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
find-up: 3.0.0
|
||||||
|
|
||||||
plist@3.1.0:
|
plist@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@xmldom/xmldom': 0.8.11
|
'@xmldom/xmldom': 0.8.11
|
||||||
@@ -11278,6 +11381,11 @@ 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-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)
|
||||||
warn-once: 0.1.1
|
warn-once: 0.1.1
|
||||||
|
|
||||||
|
react-native-segmented-control-2@2.1.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-svg@15.12.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-svg@15.12.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:
|
dependencies:
|
||||||
css-select: 5.2.2
|
css-select: 5.2.2
|
||||||
@@ -11480,6 +11588,8 @@ snapshots:
|
|||||||
rc: 1.2.8
|
rc: 1.2.8
|
||||||
resolve: 1.7.1
|
resolve: 1.7.1
|
||||||
|
|
||||||
|
reselect@4.1.8: {}
|
||||||
|
|
||||||
reserved-identifiers@1.2.0: {}
|
reserved-identifiers@1.2.0: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|||||||
@@ -1,53 +1,159 @@
|
|||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import type { SegmentedTabItem } from '@/components/common/ao/SegmentedTabs'
|
||||||
import { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Pressable, Text, View } from 'react-native'
|
import { Pressable, Text, View } from 'react-native'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
|
import { LucideIcon } from '@/components/common/ao'
|
||||||
|
import SegmentedTabs from '@/components/common/ao/SegmentedTabs'
|
||||||
|
import { Button, ButtonText } from '@/components/ui/button'
|
||||||
|
|
||||||
export default function Money() {
|
import {
|
||||||
const insets = useSafeAreaInsets()
|
Drawer,
|
||||||
const [active, setActive] = useState<'明细' | '视图'>('明细')
|
DrawerBackdrop,
|
||||||
|
DrawerBody,
|
||||||
|
DrawerCloseButton,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
} from '@/components/ui/drawer'
|
||||||
|
import { CloseIcon, Icon } from '@/components/ui/icon'
|
||||||
|
|
||||||
|
function Detail() {
|
||||||
|
const [dateSelected, setDateSelected] = useState(false)
|
||||||
|
const [filterOpen, setFilterOpen] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Detail加载了')
|
||||||
|
}, [])
|
||||||
|
// 动画角度
|
||||||
|
const dateRotation = useSharedValue(0)
|
||||||
|
const filterRotation = useSharedValue(0)
|
||||||
|
|
||||||
|
const toggleDate = () => {
|
||||||
|
setDateSelected(prev => !prev)
|
||||||
|
|
||||||
|
dateRotation.value = withTiming(dateRotation.value === 0 ? 180 : 0, { duration: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFilter = () => {
|
||||||
|
setFilterOpen(prev => !prev)
|
||||||
|
|
||||||
|
filterRotation.value = withTiming(filterRotation.value === 0 ? 180 : 0, { duration: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateAnimatedStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ rotate: `${dateRotation.value}deg` }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
const filterAnimatedStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ rotate: `${filterRotation.value}deg` }],
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinearGradient
|
<View>
|
||||||
colors={['#fffaf0', '#fefefe', '#f0fff4']}
|
<View className="flex-row justify-between">
|
||||||
start={{ x: 0, y: 0 }}
|
<Pressable
|
||||||
end={{ x: 1, y: 1 }}
|
className="flex-row items-center gap-1"
|
||||||
style={{
|
onPress={toggleDate}
|
||||||
flex: 1,
|
>
|
||||||
paddingTop: insets.top,
|
<Text style={{ fontSize: 16 }}>今天</Text>
|
||||||
paddingBottom: insets.bottom,
|
<Animated.View style={dateAnimatedStyle}>
|
||||||
paddingLeft: insets.left,
|
<LucideIcon icon="ChevronDown" size={18} />
|
||||||
paddingRight: insets.right,
|
</Animated.View>
|
||||||
}}
|
</Pressable>
|
||||||
>
|
|
||||||
<View style={{ flexDirection: 'row' }}>
|
<Pressable
|
||||||
{['明细', '视图'].map((item) => {
|
className="flex-row items-center gap-1"
|
||||||
const isActive = active === item
|
onPress={toggleFilter}
|
||||||
return (
|
>
|
||||||
<Pressable
|
<Text style={{ fontSize: 16 }}>筛选</Text>
|
||||||
key={item}
|
<Animated.View style={filterAnimatedStyle}>
|
||||||
onPress={() => setActive(item as '明细' | '视图')}
|
<LucideIcon icon="ChevronDown" size={18} />
|
||||||
style={{ alignItems: 'center', paddingVertical: 12 }}
|
</Animated.View>
|
||||||
>
|
</Pressable>
|
||||||
<Text style={{ fontSize: isActive ? 22 : 16, color: isActive ? '#333' : '#888', fontWeight: isActive ? '600' : '400' }}>
|
|
||||||
{item}
|
|
||||||
</Text>
|
|
||||||
{/* 底部指示线 */}
|
|
||||||
{isActive && (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
marginTop: 4,
|
|
||||||
height: 2,
|
|
||||||
width: 24,
|
|
||||||
backgroundColor: '#007AFF',
|
|
||||||
borderRadius: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
</View>
|
||||||
</LinearGradient>
|
|
||||||
|
<Drawer
|
||||||
|
isOpen={dateSelected}
|
||||||
|
anchor="bottom"
|
||||||
|
onClose={() => {
|
||||||
|
setDateSelected(false)
|
||||||
|
}}
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<DrawerBackdrop />
|
||||||
|
<DrawerContent style={{ padding: 12 }}>
|
||||||
|
<DrawerBody style={{ padding: 0, marginTop: 0, marginBottom: 0 }}>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
<Text>This is the basic drawer component.</Text>
|
||||||
|
</DrawerBody>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Chart() {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Chart记载了')
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text>这里是视图内这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容容</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MoneyTest() {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
|
const tabs: SegmentedTabItem[] = [
|
||||||
|
{
|
||||||
|
label: '明细',
|
||||||
|
icon: 'ScrollText',
|
||||||
|
content: <Detail />, // ✅ 注意这里不要调用 Detail()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '视图',
|
||||||
|
icon: 'ChartPie',
|
||||||
|
content: <Chart />, // ✅ 注意这里不要调用 Chart()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '视图2',
|
||||||
|
icon: 'ChartPie',
|
||||||
|
content: (
|
||||||
|
<View>
|
||||||
|
<Text>这里是视图内容2</Text>
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={{ flex: 1, paddingTop: 4, paddingLeft: 8, paddingRight: 8 }}>
|
||||||
|
<SegmentedTabs
|
||||||
|
headerWidth={220}
|
||||||
|
tabs={tabs}
|
||||||
|
value={selectedIndex}
|
||||||
|
onChange={setSelectedIndex}
|
||||||
|
cacheContent={true}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { GestureResponderEvent, ViewStyle } from 'react-native'
|
||||||
|
import { icons } from 'lucide-react-native'
|
||||||
|
import React from 'react'
|
||||||
|
import { Pressable } from 'react-native'
|
||||||
|
|
||||||
|
interface LucideIconProps {
|
||||||
|
icon: keyof typeof icons
|
||||||
|
size?: number
|
||||||
|
color?: string
|
||||||
|
strokeWidth?: number
|
||||||
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
|
style?: ViewStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LucideIcon({
|
||||||
|
icon,
|
||||||
|
size = 16,
|
||||||
|
color = '#000',
|
||||||
|
strokeWidth = 2,
|
||||||
|
onPress,
|
||||||
|
style,
|
||||||
|
}: LucideIconProps) {
|
||||||
|
const IconComponent = icons[icon]
|
||||||
|
|
||||||
|
if (!IconComponent)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable onPress={onPress} style={style}>
|
||||||
|
<IconComponent size={size} color={color} strokeWidth={strokeWidth} />
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
import type { icons } from 'lucide-react-native'
|
||||||
|
import type { LayoutChangeEvent, StyleProp, TextStyle, ViewStyle } from 'react-native'
|
||||||
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import { Platform, Pressable, Text, View } from 'react-native'
|
||||||
|
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated'
|
||||||
|
import LucideIcon from './LucideIcon'
|
||||||
|
|
||||||
|
export interface SegmentedTabItem {
|
||||||
|
label: string
|
||||||
|
icon?: keyof typeof icons
|
||||||
|
content?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SegmentedTabsProps {
|
||||||
|
tabs: SegmentedTabItem[]
|
||||||
|
value?: number
|
||||||
|
defaultIndex?: number
|
||||||
|
onChange?: (index: number) => void
|
||||||
|
|
||||||
|
className?: string
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
|
||||||
|
tabHeight?: number
|
||||||
|
borderRadius?: number
|
||||||
|
spacing?: number
|
||||||
|
|
||||||
|
sliderColor?: string
|
||||||
|
sliderStyle?: StyleProp<ViewStyle>
|
||||||
|
|
||||||
|
selectedTextStyle?: StyleProp<TextStyle>
|
||||||
|
unselectedTextStyle?: StyleProp<TextStyle>
|
||||||
|
selectedColor?: string
|
||||||
|
unselectedColor?: string
|
||||||
|
fontSize?: number
|
||||||
|
iconSize?: number
|
||||||
|
|
||||||
|
contentContainerStyle?: StyleProp<ViewStyle>
|
||||||
|
animateContent?: boolean
|
||||||
|
cacheContent?: boolean
|
||||||
|
|
||||||
|
headerWidth?: number
|
||||||
|
contentWidth?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SegmentedTabs({
|
||||||
|
tabs,
|
||||||
|
value,
|
||||||
|
defaultIndex = 0,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
tabHeight = 36,
|
||||||
|
borderRadius = 2,
|
||||||
|
spacing = 4,
|
||||||
|
sliderColor = '#fff',
|
||||||
|
sliderStyle,
|
||||||
|
selectedTextStyle,
|
||||||
|
unselectedTextStyle,
|
||||||
|
selectedColor = '#000',
|
||||||
|
unselectedColor = '#666',
|
||||||
|
fontSize = 14,
|
||||||
|
iconSize = 14,
|
||||||
|
contentContainerStyle,
|
||||||
|
animateContent = true,
|
||||||
|
cacheContent = true,
|
||||||
|
headerWidth,
|
||||||
|
contentWidth,
|
||||||
|
}: SegmentedTabsProps) {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(value ?? defaultIndex)
|
||||||
|
const [headerMeasuredWidth, setHeaderMeasuredWidth] = useState(0)
|
||||||
|
const [contentMeasuredWidth, setContentMeasuredWidth] = useState(0)
|
||||||
|
const [ready, setReady] = useState(false)
|
||||||
|
|
||||||
|
const sliderX = useSharedValue(0)
|
||||||
|
const contentX = useSharedValue(0)
|
||||||
|
|
||||||
|
const initialized = useRef(false)
|
||||||
|
|
||||||
|
const actualHeaderWidth = headerWidth ?? headerMeasuredWidth
|
||||||
|
const actualContentWidth = contentWidth ?? contentMeasuredWidth
|
||||||
|
const tabWidth = actualHeaderWidth ? actualHeaderWidth / tabs.length : 0
|
||||||
|
const sliderWidth = tabWidth - spacing * 2
|
||||||
|
const sliderHeight = tabHeight - spacing * 2
|
||||||
|
|
||||||
|
// 外部 value 改变
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== undefined)
|
||||||
|
setSelectedIndex(value)
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
// 滑块和内容动画
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ready || tabWidth === 0 || actualContentWidth === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
const target = selectedIndex * tabWidth + spacing
|
||||||
|
|
||||||
|
if (!initialized.current) {
|
||||||
|
sliderX.value = target
|
||||||
|
contentX.value = -selectedIndex * actualContentWidth
|
||||||
|
initialized.current = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sliderX.value = withSpring(target, { damping: 18, stiffness: 150, mass: 1 })
|
||||||
|
contentX.value = animateContent
|
||||||
|
? withTiming(-selectedIndex * actualContentWidth, { duration: 250 })
|
||||||
|
: -selectedIndex * actualContentWidth
|
||||||
|
}
|
||||||
|
}, [selectedIndex, tabWidth, actualContentWidth, ready])
|
||||||
|
|
||||||
|
const animatedSliderStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ translateX: sliderX.value }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
const animatedContentStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ translateX: contentX.value }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handlePress = (index: number) => {
|
||||||
|
if (value === undefined)
|
||||||
|
setSelectedIndex(index)
|
||||||
|
onChange?.(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleHeaderLayout = (event: LayoutChangeEvent) => {
|
||||||
|
const { width } = event.nativeEvent.layout
|
||||||
|
setHeaderMeasuredWidth(width)
|
||||||
|
if (width > 0 && actualContentWidth > 0 && !ready)
|
||||||
|
setReady(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContentLayout = (event: LayoutChangeEvent) => {
|
||||||
|
const { width } = event.nativeEvent.layout
|
||||||
|
setContentMeasuredWidth(width)
|
||||||
|
if (width > 0 && actualHeaderWidth > 0 && !ready)
|
||||||
|
setReady(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="w-full h-full">
|
||||||
|
{/* Header */}
|
||||||
|
<View
|
||||||
|
className={className}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
width: headerWidth,
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: '#f2f2f2',
|
||||||
|
borderRadius,
|
||||||
|
height: tabHeight,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
onLayout={handleHeaderLayout}
|
||||||
|
>
|
||||||
|
{/* 滑块 */}
|
||||||
|
{ready && (
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
position: 'absolute',
|
||||||
|
top: spacing,
|
||||||
|
left: 0,
|
||||||
|
width: sliderWidth,
|
||||||
|
height: sliderHeight,
|
||||||
|
borderRadius,
|
||||||
|
backgroundColor: sliderColor,
|
||||||
|
...Platform.select({ android: { elevation: 6 } }),
|
||||||
|
},
|
||||||
|
sliderStyle,
|
||||||
|
animatedSliderStyle,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => {
|
||||||
|
const isActive = i === selectedIndex
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
key={i}
|
||||||
|
onPress={() => handlePress(i)}
|
||||||
|
style={{
|
||||||
|
width: tabWidth,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: spacing,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.icon && (
|
||||||
|
<LucideIcon
|
||||||
|
icon={tab.icon}
|
||||||
|
size={iconSize}
|
||||||
|
color={isActive ? selectedColor : unselectedColor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
{ color: isActive ? selectedColor : unselectedColor, fontSize, fontWeight: 'bold' },
|
||||||
|
isActive ? selectedTextStyle : unselectedTextStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{ width: contentWidth ?? '100%', flex: 1, overflow: 'hidden', marginTop: 16 },
|
||||||
|
contentContainerStyle,
|
||||||
|
]}
|
||||||
|
onLayout={handleContentLayout}
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{ flexDirection: 'row', width: actualContentWidth * tabs.length },
|
||||||
|
animatedContentStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => {
|
||||||
|
const isActive = i === selectedIndex
|
||||||
|
const shouldRender = cacheContent || isActive
|
||||||
|
|
||||||
|
if (!shouldRender) {
|
||||||
|
// 占位保持宽度
|
||||||
|
return <View key={`placeholder-${i}`} style={{ width: actualContentWidth }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={isActive ? `tab-${i}-${selectedIndex}` : i} // ✅ 动态 key,确保重新挂载
|
||||||
|
style={{ width: actualContentWidth }}
|
||||||
|
>
|
||||||
|
{tab.content}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import Checkbox from './Checkbox'
|
|||||||
import Form, { FormRef } from './Form'
|
import Form, { FormRef } from './Form'
|
||||||
import FormItem from './FormItem'
|
import FormItem from './FormItem'
|
||||||
import Input from './Input'
|
import Input from './Input'
|
||||||
|
import LucideIcon from './LucideIcon'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Button as AButton,
|
Button as AButton,
|
||||||
@@ -11,4 +12,5 @@ export {
|
|||||||
FormItem as AFormItem,
|
FormItem as AFormItem,
|
||||||
FormRef as AFormRef,
|
FormRef as AFormRef,
|
||||||
Input as AInput,
|
Input as AInput,
|
||||||
|
LucideIcon,
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
'use client'
|
||||||
|
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'
|
||||||
|
import type {
|
||||||
|
MotionComponentProps,
|
||||||
|
} from '@legendapp/motion'
|
||||||
|
import type {
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native'
|
||||||
|
import { createModal as createDrawer } from '@gluestack-ui/core/modal/creator'
|
||||||
|
import { tva, useStyleContext, withStyleContext } from '@gluestack-ui/utils/nativewind-utils'
|
||||||
|
import {
|
||||||
|
AnimatePresence,
|
||||||
|
createMotionAnimatedComponent,
|
||||||
|
Motion,
|
||||||
|
} from '@legendapp/motion'
|
||||||
|
|
||||||
|
import { cssInterop } from 'nativewind'
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
Dimensions,
|
||||||
|
Pressable,
|
||||||
|
ScrollView,
|
||||||
|
View,
|
||||||
|
} from 'react-native'
|
||||||
|
|
||||||
|
type IAnimatedPressableProps = React.ComponentProps<typeof Pressable>
|
||||||
|
& MotionComponentProps<typeof Pressable, ViewStyle, unknown, unknown, unknown>
|
||||||
|
|
||||||
|
const AnimatedPressable = createMotionAnimatedComponent(
|
||||||
|
Pressable,
|
||||||
|
) as React.ComponentType<IAnimatedPressableProps>
|
||||||
|
|
||||||
|
const SCOPE = 'MODAL'
|
||||||
|
const screenWidth = Dimensions.get('window').width
|
||||||
|
const screenHeight = Dimensions.get('window').height
|
||||||
|
const sizes: { [key: string]: number } = {
|
||||||
|
sm: 0.25,
|
||||||
|
md: 0.5,
|
||||||
|
lg: 0.75,
|
||||||
|
full: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
type IMotionViewProps = React.ComponentProps<typeof View>
|
||||||
|
& MotionComponentProps<typeof View, ViewStyle, unknown, unknown, unknown>
|
||||||
|
|
||||||
|
const MotionView = Motion.View as React.ComponentType<IMotionViewProps>
|
||||||
|
|
||||||
|
const UIDrawer = createDrawer({
|
||||||
|
Root: withStyleContext(View, SCOPE),
|
||||||
|
Backdrop: AnimatedPressable,
|
||||||
|
Content: MotionView,
|
||||||
|
Body: ScrollView,
|
||||||
|
CloseButton: Pressable,
|
||||||
|
Footer: View,
|
||||||
|
Header: View,
|
||||||
|
AnimatePresence,
|
||||||
|
})
|
||||||
|
|
||||||
|
cssInterop(AnimatedPressable, { className: 'style' })
|
||||||
|
cssInterop(MotionView, { className: 'style' })
|
||||||
|
|
||||||
|
const drawerStyle = tva({
|
||||||
|
base: 'w-full h-full web:pointer-events-none relative',
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: '',
|
||||||
|
md: '',
|
||||||
|
lg: '',
|
||||||
|
full: '',
|
||||||
|
},
|
||||||
|
anchor: {
|
||||||
|
left: 'items-start',
|
||||||
|
right: 'items-end',
|
||||||
|
top: 'justify-start',
|
||||||
|
bottom: 'justify-end',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const drawerBackdropStyle = tva({
|
||||||
|
base: 'absolute left-0 top-0 right-0 bottom-0 bg-background-dark web:cursor-default',
|
||||||
|
})
|
||||||
|
|
||||||
|
const drawerContentStyle = tva({
|
||||||
|
base: 'bg-background-0 overflow-scroll border-outline-100 p-6 absolute',
|
||||||
|
parentVariants: {
|
||||||
|
size: {
|
||||||
|
sm: 'w-1/4',
|
||||||
|
md: 'w-1/2',
|
||||||
|
lg: 'w-3/4',
|
||||||
|
full: 'w-full',
|
||||||
|
},
|
||||||
|
anchor: {
|
||||||
|
left: 'h-full border-r',
|
||||||
|
right: 'h-full border-l',
|
||||||
|
top: 'w-full border-b',
|
||||||
|
bottom: 'w-full border-t',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parentCompoundVariants: [
|
||||||
|
{
|
||||||
|
anchor: 'top',
|
||||||
|
size: 'sm',
|
||||||
|
class: 'h-1/4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'top',
|
||||||
|
size: 'md',
|
||||||
|
class: 'h-1/2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'top',
|
||||||
|
size: 'lg',
|
||||||
|
class: 'h-3/4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'top',
|
||||||
|
size: 'full',
|
||||||
|
class: 'h-full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'bottom',
|
||||||
|
size: 'sm',
|
||||||
|
class: 'h-1/4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'bottom',
|
||||||
|
size: 'md',
|
||||||
|
class: 'h-1/2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'bottom',
|
||||||
|
size: 'lg',
|
||||||
|
class: 'h-3/4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'bottom',
|
||||||
|
size: 'full',
|
||||||
|
class: 'h-full',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const drawerCloseButtonStyle = tva({
|
||||||
|
base: 'z-10 rounded data-[focus-visible=true]:web:bg-background-100 web:outline-0 cursor-pointer',
|
||||||
|
})
|
||||||
|
|
||||||
|
const drawerHeaderStyle = tva({
|
||||||
|
base: 'justify-between items-center flex-row',
|
||||||
|
})
|
||||||
|
|
||||||
|
const drawerBodyStyle = tva({
|
||||||
|
base: 'mt-4 mb-6 shrink-0',
|
||||||
|
})
|
||||||
|
|
||||||
|
const drawerFooterStyle = tva({
|
||||||
|
base: 'flex-row justify-end items-center',
|
||||||
|
})
|
||||||
|
|
||||||
|
type IDrawerProps = React.ComponentProps<typeof UIDrawer>
|
||||||
|
& VariantProps<typeof drawerStyle> & { className?: string }
|
||||||
|
|
||||||
|
type IDrawerBackdropProps = React.ComponentProps<typeof UIDrawer.Backdrop>
|
||||||
|
& VariantProps<typeof drawerBackdropStyle> & { className?: string }
|
||||||
|
|
||||||
|
type IDrawerContentProps = React.ComponentProps<typeof UIDrawer.Content>
|
||||||
|
& VariantProps<typeof drawerContentStyle> & { className?: string }
|
||||||
|
|
||||||
|
type IDrawerHeaderProps = React.ComponentProps<typeof UIDrawer.Header>
|
||||||
|
& VariantProps<typeof drawerHeaderStyle> & { className?: string }
|
||||||
|
|
||||||
|
type IDrawerBodyProps = React.ComponentProps<typeof UIDrawer.Body>
|
||||||
|
& VariantProps<typeof drawerBodyStyle> & { className?: string }
|
||||||
|
|
||||||
|
type IDrawerFooterProps = React.ComponentProps<typeof UIDrawer.Footer>
|
||||||
|
& VariantProps<typeof drawerFooterStyle> & { className?: string }
|
||||||
|
|
||||||
|
type IDrawerCloseButtonProps = React.ComponentProps<
|
||||||
|
typeof UIDrawer.CloseButton
|
||||||
|
>
|
||||||
|
& VariantProps<typeof drawerCloseButtonStyle> & { className?: string }
|
||||||
|
|
||||||
|
const Drawer = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer>,
|
||||||
|
IDrawerProps
|
||||||
|
>(({ className, size = 'sm', anchor = 'left', ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<UIDrawer
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
pointerEvents="box-none"
|
||||||
|
className={drawerStyle({ size, anchor, class: className })}
|
||||||
|
context={{ size, anchor }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const DrawerBackdrop = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer.Backdrop>,
|
||||||
|
IDrawerBackdropProps
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<UIDrawer.Backdrop
|
||||||
|
ref={ref}
|
||||||
|
initial={{
|
||||||
|
opacity: 0,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
opacity: 0.5,
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
type: 'spring',
|
||||||
|
damping: 18,
|
||||||
|
stiffness: 250,
|
||||||
|
opacity: {
|
||||||
|
type: 'timing',
|
||||||
|
duration: 250,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
className={drawerBackdropStyle({
|
||||||
|
class: className,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const DrawerContent = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer.Content>,
|
||||||
|
IDrawerContentProps
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { size: parentSize, anchor: parentAnchor } = useStyleContext(SCOPE)
|
||||||
|
|
||||||
|
const drawerHeight = screenHeight * (sizes[parentSize] || sizes.md)
|
||||||
|
const drawerWidth = screenWidth * (sizes[parentSize] || sizes.md)
|
||||||
|
|
||||||
|
const isHorizontal = parentAnchor === 'left' || parentAnchor === 'right'
|
||||||
|
|
||||||
|
const initialObj = isHorizontal
|
||||||
|
? { x: parentAnchor === 'left' ? -drawerWidth : drawerWidth }
|
||||||
|
: { y: parentAnchor === 'top' ? -drawerHeight : drawerHeight }
|
||||||
|
|
||||||
|
const animateObj = isHorizontal ? { x: 0 } : { y: 0 }
|
||||||
|
|
||||||
|
const exitObj = isHorizontal
|
||||||
|
? { x: parentAnchor === 'left' ? -drawerWidth : drawerWidth }
|
||||||
|
: { y: parentAnchor === 'top' ? -drawerHeight : drawerHeight }
|
||||||
|
|
||||||
|
const customClass = isHorizontal
|
||||||
|
? `top-0 ${parentAnchor === 'left' ? 'left-0' : 'right-0'}`
|
||||||
|
: `left-0 ${parentAnchor === 'top' ? 'top-0' : 'bottom-0'}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UIDrawer.Content
|
||||||
|
ref={ref}
|
||||||
|
initial={initialObj}
|
||||||
|
animate={animateObj}
|
||||||
|
exit={exitObj}
|
||||||
|
transition={{
|
||||||
|
type: 'timing',
|
||||||
|
duration: 300,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
className={drawerContentStyle({
|
||||||
|
parentVariants: {
|
||||||
|
size: parentSize,
|
||||||
|
anchor: parentAnchor,
|
||||||
|
},
|
||||||
|
class: `${className} ${customClass}`,
|
||||||
|
})}
|
||||||
|
pointerEvents="auto"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const DrawerHeader = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer.Header>,
|
||||||
|
IDrawerHeaderProps
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<UIDrawer.Header
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={drawerHeaderStyle({
|
||||||
|
class: className,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const DrawerBody = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer.Body>,
|
||||||
|
IDrawerBodyProps
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<UIDrawer.Body
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={drawerBodyStyle({
|
||||||
|
class: className,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const DrawerFooter = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer.Footer>,
|
||||||
|
IDrawerFooterProps
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<UIDrawer.Footer
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={drawerFooterStyle({
|
||||||
|
class: className,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const DrawerCloseButton = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof UIDrawer.CloseButton>,
|
||||||
|
IDrawerCloseButtonProps
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<UIDrawer.CloseButton
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={drawerCloseButtonStyle({
|
||||||
|
class: className,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
Drawer.displayName = 'Drawer'
|
||||||
|
DrawerBackdrop.displayName = 'DrawerBackdrop'
|
||||||
|
DrawerContent.displayName = 'DrawerContent'
|
||||||
|
DrawerHeader.displayName = 'DrawerHeader'
|
||||||
|
DrawerBody.displayName = 'DrawerBody'
|
||||||
|
DrawerFooter.displayName = 'DrawerFooter'
|
||||||
|
DrawerCloseButton.displayName = 'DrawerCloseButton'
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerBackdrop,
|
||||||
|
DrawerBody,
|
||||||
|
DrawerCloseButton,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ function isWhiteListed(url: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const alovaInstance = createAlova({
|
const alovaInstance = createAlova({
|
||||||
baseURL: 'http://192.168.1.136:8085/app',
|
baseURL: 'http://192.168.5.6:8085/app',
|
||||||
requestAdapter: axiosRequestAdapter(),
|
requestAdapter: axiosRequestAdapter(),
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
responded: {
|
responded: {
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>账单卡片 + 日期筛选</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
background: #f7f8fa;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部搜索 + 筛选 */
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #f2f3f7;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
fill: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn {
|
||||||
|
background: #007aff;
|
||||||
|
color: #fff;
|
||||||
|
padding: 7px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片 */
|
||||||
|
.card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 3px 8px rgba(0,0,0,0.08);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-amount {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #222;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-summary {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠日期筛选面板 */
|
||||||
|
.filter-panel {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
box-shadow: 0 3px 8px rgba(0,0,0,0.08);
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.35s ease;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel.show {
|
||||||
|
max-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel .panel-title {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel .date-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel .panel-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel .btn {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.reset {
|
||||||
|
background-color: #f2f3f7;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.confirm {
|
||||||
|
background-color: #007aff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- 搜索 + 筛选 -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<div class="search-box">
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M10,2A8,8 0 0,1 18,10C18,11.85 17.37,13.55 16.28,14.86L22,20.59L20.59,22L14.86,16.28C13.55,17.37 11.85,18 10,18A8,8 0 0,1 2,10A8,8 0 0,1 10,2Z"/></svg>
|
||||||
|
<input type="text" placeholder="搜索备注、分类...">
|
||||||
|
</div>
|
||||||
|
<div class="filter-btn" onclick="togglePanel()">筛选</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 日期筛选面板 -->
|
||||||
|
<div id="filterPanel" class="filter-panel">
|
||||||
|
<div class="panel-title">选择日期</div>
|
||||||
|
<input type="date" id="dateInput" class="date-input">
|
||||||
|
|
||||||
|
<div class="panel-actions">
|
||||||
|
<div class="btn reset" onclick="resetDate()">重置</div>
|
||||||
|
<div class="btn confirm" onclick="applyDate()">确定</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 卡片 -->
|
||||||
|
<div class="card">
|
||||||
|
<div id="cardDate" class="card-date">当前周期:本月</div>
|
||||||
|
<div id="cardRemaining" class="card-amount">剩余:¥2500</div>
|
||||||
|
<div class="card-summary">
|
||||||
|
<div>收入:¥5000</div>
|
||||||
|
<div>支出:¥2500</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const panel = document.getElementById('filterPanel');
|
||||||
|
const cardDate = document.getElementById('cardDate');
|
||||||
|
const cardRemaining = document.getElementById('cardRemaining');
|
||||||
|
|
||||||
|
function togglePanel() {
|
||||||
|
panel.classList.toggle('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDate() {
|
||||||
|
document.getElementById('dateInput').value = '';
|
||||||
|
cardDate.innerText = "当前周期:本月";
|
||||||
|
cardRemaining.innerText = "剩余:¥2500";
|
||||||
|
panel.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDate() {
|
||||||
|
const dateVal = document.getElementById('dateInput').value;
|
||||||
|
if(dateVal) {
|
||||||
|
// 模拟根据日期计算的金额,这里可替换为真实数据逻辑
|
||||||
|
cardDate.innerText = `当前周期:${dateVal}`;
|
||||||
|
cardRemaining.innerText = `剩余:¥${Math.floor(Math.random()*5000)}`;
|
||||||
|
}
|
||||||
|
panel.classList.remove('show');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 287 KiB |
Reference in new issue
Block a user