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' }],
|
||||
'nativewind/babel',
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['./src'],
|
||||
alias: {
|
||||
'@': './src',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -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-segmented-control/segmented-control": "2.5.7",
|
||||
"alova": "^3.3.4",
|
||||
"axios": "^1.13.2",
|
||||
"expo": "~54.0.20",
|
||||
@@ -35,12 +36,14 @@
|
||||
"react-native-reanimated": "~4.1.3",
|
||||
"react-native-safe-area-context": "~5.6.1",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-segmented-control-2": "^2.1.1",
|
||||
"react-native-svg": "15.12.0",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^6.2.0",
|
||||
"@types/react": "~19.1.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^9.38.0",
|
||||
"react-native-iconfont-cli": "^2.2.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
|
||||
+118
-8
@@ -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-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:
|
||||
specifier: ^3.3.4
|
||||
version: 3.3.4
|
||||
@@ -83,6 +86,9 @@ importers:
|
||||
react-native-screens:
|
||||
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)
|
||||
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:
|
||||
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)
|
||||
@@ -96,6 +102,9 @@ importers:
|
||||
'@types/react':
|
||||
specifier: ~19.1.0
|
||||
version: 19.1.17
|
||||
babel-plugin-module-resolver:
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
eslint:
|
||||
specifier: ^9.38.0
|
||||
version: 9.39.0(jiti@1.21.7)
|
||||
@@ -1571,6 +1580,12 @@ packages:
|
||||
peerDependencies:
|
||||
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':
|
||||
resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==}
|
||||
engines: {node: '>= 20.19.4'}
|
||||
@@ -2257,6 +2272,9 @@ packages:
|
||||
resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==}
|
||||
peerDependencies:
|
||||
@@ -3198,10 +3216,17 @@ packages:
|
||||
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
find-babel-config@2.1.2:
|
||||
resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==}
|
||||
|
||||
find-up-simple@1.0.1:
|
||||
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
find-up@3.0.0:
|
||||
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
find-up@4.1.0:
|
||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3324,6 +3349,10 @@ packages:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
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:
|
||||
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3690,56 +3719,48 @@ 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==}
|
||||
@@ -3784,6 +3805,10 @@ packages:
|
||||
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
locate-path@3.0.0:
|
||||
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4131,6 +4156,10 @@ packages:
|
||||
minimatch@3.1.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -4138,6 +4167,10 @@ packages:
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
minipass@4.2.8:
|
||||
resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -4291,6 +4324,10 @@ packages:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4331,6 +4368,10 @@ packages:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
path-exists@3.0.0:
|
||||
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4382,6 +4423,10 @@ packages:
|
||||
pkg-types@2.3.0:
|
||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||
|
||||
pkg-up@3.1.0:
|
||||
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
plist@3.1.0:
|
||||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||
engines: {node: '>=10.4.0'}
|
||||
@@ -4601,6 +4646,12 @@ packages:
|
||||
react: '*'
|
||||
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:
|
||||
resolution: {integrity: sha512-iE25PxIJ6V0C6krReLquVw6R0QTsRTmEQc4K2Co3P6zsimU/jltcDBKYDy1h/5j9S/fqmMeXnpM+9LEWKJKI6A==}
|
||||
peerDependencies:
|
||||
@@ -4726,6 +4777,9 @@ packages:
|
||||
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
reselect@4.1.8:
|
||||
resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==}
|
||||
|
||||
reserved-identifiers@1.2.0:
|
||||
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -7517,6 +7571,11 @@ 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-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/babel-plugin-codegen@0.81.5(@babel/core@7.28.5)':
|
||||
@@ -8447,6 +8506,14 @@ snapshots:
|
||||
'@types/babel__core': 7.20.5
|
||||
'@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):
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.28.5
|
||||
@@ -9501,8 +9568,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
find-babel-config@2.1.2:
|
||||
dependencies:
|
||||
json5: 2.2.3
|
||||
|
||||
find-up-simple@1.0.1: {}
|
||||
|
||||
find-up@3.0.0:
|
||||
dependencies:
|
||||
locate-path: 3.0.0
|
||||
|
||||
find-up@4.1.0:
|
||||
dependencies:
|
||||
locate-path: 5.0.0
|
||||
@@ -9628,6 +9703,13 @@ snapshots:
|
||||
once: 1.4.0
|
||||
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:
|
||||
dependencies:
|
||||
ini: 1.3.8
|
||||
@@ -10047,6 +10129,11 @@ snapshots:
|
||||
pkg-types: 2.3.0
|
||||
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:
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
@@ -10795,12 +10882,18 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@8.0.4:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
minipass@4.2.8: {}
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
minizlib@3.1.0:
|
||||
@@ -10950,6 +11043,10 @@ snapshots:
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-locate@3.0.0:
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
|
||||
p-locate@4.1.0:
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
@@ -10982,6 +11079,8 @@ snapshots:
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
|
||||
path-exists@3.0.0: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
@@ -11021,6 +11120,10 @@ snapshots:
|
||||
exsolve: 1.0.7
|
||||
pathe: 2.0.3
|
||||
|
||||
pkg-up@3.1.0:
|
||||
dependencies:
|
||||
find-up: 3.0.0
|
||||
|
||||
plist@3.1.0:
|
||||
dependencies:
|
||||
'@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)
|
||||
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):
|
||||
dependencies:
|
||||
css-select: 5.2.2
|
||||
@@ -11480,6 +11588,8 @@ snapshots:
|
||||
rc: 1.2.8
|
||||
resolve: 1.7.1
|
||||
|
||||
reselect@4.1.8: {}
|
||||
|
||||
reserved-identifiers@1.2.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
@@ -1,53 +1,159 @@
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import { useState } from 'react'
|
||||
import type { SegmentedTabItem } from '@/components/common/ao/SegmentedTabs'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
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() {
|
||||
const insets = useSafeAreaInsets()
|
||||
const [active, setActive] = useState<'明细' | '视图'>('明细')
|
||||
import {
|
||||
Drawer,
|
||||
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 (
|
||||
<LinearGradient
|
||||
colors={['#fffaf0', '#fefefe', '#f0fff4']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingTop: insets.top,
|
||||
paddingBottom: insets.bottom,
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
{['明细', '视图'].map((item) => {
|
||||
const isActive = active === item
|
||||
return (
|
||||
<Pressable
|
||||
key={item}
|
||||
onPress={() => setActive(item as '明细' | '视图')}
|
||||
style={{ alignItems: 'center', paddingVertical: 12 }}
|
||||
>
|
||||
<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 className="flex-row justify-between">
|
||||
<Pressable
|
||||
className="flex-row items-center gap-1"
|
||||
onPress={toggleDate}
|
||||
>
|
||||
<Text style={{ fontSize: 16 }}>今天</Text>
|
||||
<Animated.View style={dateAnimatedStyle}>
|
||||
<LucideIcon icon="ChevronDown" size={18} />
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
className="flex-row items-center gap-1"
|
||||
onPress={toggleFilter}
|
||||
>
|
||||
<Text style={{ fontSize: 16 }}>筛选</Text>
|
||||
<Animated.View style={filterAnimatedStyle}>
|
||||
<LucideIcon icon="ChevronDown" size={18} />
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
</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 FormItem from './FormItem'
|
||||
import Input from './Input'
|
||||
import LucideIcon from './LucideIcon'
|
||||
|
||||
export {
|
||||
Button as AButton,
|
||||
@@ -11,4 +12,5 @@ export {
|
||||
FormItem as AFormItem,
|
||||
FormRef as AFormRef,
|
||||
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({
|
||||
baseURL: 'http://192.168.1.136:8085/app',
|
||||
baseURL: 'http://192.168.5.6:8085/app',
|
||||
requestAdapter: axiosRequestAdapter(),
|
||||
timeout: 5000,
|
||||
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>
|
||||
Reference in new issue
Block a user