This commit is contained in:
oneao committed 2025-12-01 22:27:11 +08:00
1 parent 494f1ad01f
commit 0ab2796dd4
21 files changed
+1583 -472

No files matched your search

+2
View File
@@ -11,6 +11,7 @@
},
"dependencies": {
"@alova/adapter-axios": "^2.0.16",
"@expo/html-elements": "0.12.5",
"@gluestack-ui/core": "^3.0.12",
"@gluestack-ui/utils": "^3.0.11",
"@gluestack/ui-next-adapter": "^3.0.3",
@@ -44,6 +45,7 @@
"react-native-modal": "14.0.0-rc.1",
"react-native-pager-view": "6.9.1",
"react-native-popup-menu": "^0.18.0",
"react-native-qrcode-svg": "^6.3.20",
"react-native-reanimated": "~4.1.3",
"react-native-safe-area-context": "~5.6.1",
"react-native-screens": "~4.16.0",
+127 -8
View File
@@ -11,6 +11,9 @@ importers:
'@alova/adapter-axios':
specifier: ^2.0.16
version: 2.0.16(alova@3.3.4)(axios@1.13.2)
'@expo/html-elements':
specifier: 0.12.5
version: 0.12.5
'@gluestack-ui/core':
specifier: ^3.0.12
version: 3.0.12(@gluestack-ui/utils@3.0.12(react-dom@19.1.0(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -110,6 +113,9 @@ importers:
react-native-popup-menu:
specifier: ^0.18.0
version: 0.18.0
react-native-qrcode-svg:
specifier: ^6.3.20
version: 6.3.20(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-reanimated:
specifier: ~4.1.3
version: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -875,6 +881,9 @@ packages:
resolution: {integrity: sha512-8YPJpEYlmV171fi+t+cSLMX1nC5ngY9j2FiN70dHldLpd6Ct6ouGhk96svJ4BQZwsqwII2pokwzrDAwqo4Z0FQ==}
hasBin: true
'@expo/html-elements@0.12.5':
resolution: {integrity: sha512-28KWO88YKykKU7ke5sEQs5TivFRMs1Aktz13xxgqAf5rTgb+lka0VKVt3W2fG7ksbUQ407rtUqz7SEAq298NvQ==}
'@expo/image-utils@0.8.7':
resolution: {integrity: sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==}
@@ -2631,6 +2640,9 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -2814,6 +2826,10 @@ packages:
supports-color:
optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
@@ -2884,6 +2900,9 @@ packages:
resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
@@ -3911,56 +3930,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==}
@@ -4665,6 +4676,10 @@ packages:
resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==}
engines: {node: '>=4.0.0'}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
pnpm-workspace-yaml@1.3.0:
resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==}
@@ -4763,6 +4778,11 @@ packages:
resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==}
hasBin: true
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
@@ -4885,6 +4905,13 @@ packages:
react-native-popup-menu@0.18.0:
resolution: {integrity: sha512-XD3gyp/hwL5YWw2J034DerfvbJ6S2YHqAzvqAFRU+UGnFD4JyTqggfm4UyynBD26O1PDoRb45Loil+NZCGfCnA==}
react-native-qrcode-svg@6.3.20:
resolution: {integrity: sha512-GDP7T3DDEHUo53xmDyWWXXm3MhWRp0YayqVMum98JYfm0DW2rEbz1GIj+XLaA0QX443G0nURyzPnuQJeHjuvjA==}
peerDependencies:
react: '*'
react-native: '>=0.63.4'
react-native-svg: '>=14.0.0'
react-native-reanimated@4.1.5:
resolution: {integrity: sha512-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q==}
peerDependencies:
@@ -5040,6 +5067,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
requireg@0.2.2:
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
engines: {node: '>= 4.0.0'}
@@ -5148,6 +5178,9 @@ packages:
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
@@ -5375,6 +5408,10 @@ packages:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
text-encoding@0.7.0:
resolution: {integrity: sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==}
deprecated: no longer maintained
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
@@ -5609,6 +5646,9 @@ packages:
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -5621,6 +5661,10 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -5695,6 +5739,9 @@ packages:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -5715,10 +5762,18 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
@@ -6698,6 +6753,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@expo/html-elements@0.12.5': {}
'@expo/image-utils@0.8.7':
dependencies:
'@expo/spawn-async': 1.7.2
@@ -9139,6 +9196,12 @@ snapshots:
client-only@0.0.1: {}
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -9310,6 +9373,8 @@ snapshots:
dependencies:
ms: 2.1.3
decamelize@1.2.0: {}
decimal.js@10.6.0: {}
decode-named-character-reference@1.2.0:
@@ -9358,6 +9423,8 @@ snapshots:
diff-sequences@27.5.1: {}
dijkstrajs@1.0.3: {}
dlv@1.1.3: {}
dom-helpers@6.0.1:
@@ -11581,6 +11648,8 @@ snapshots:
pngjs@3.4.0: {}
pngjs@5.0.0: {}
pnpm-workspace-yaml@1.3.0:
dependencies:
yaml: 2.8.1
@@ -11668,6 +11737,12 @@ snapshots:
qrcode-terminal@0.11.0: {}
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
quansync@0.2.11: {}
query-string@7.1.3:
@@ -11841,6 +11916,15 @@ snapshots:
react-native-popup-menu@0.18.0: {}
react-native-qrcode-svg@6.3.20(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
prop-types: 15.8.1
qrcode: 1.5.4
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.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
text-encoding: 0.7.0
react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
'@babel/core': 7.28.5
@@ -12080,6 +12164,8 @@ snapshots:
require-from-string@2.0.2: {}
require-main-filename@2.0.0: {}
requireg@0.2.2:
dependencies:
nested-error-stacks: 2.0.1
@@ -12198,6 +12284,8 @@ snapshots:
server-only@0.0.1: {}
set-blocking@2.0.0: {}
setimmediate@1.0.5: {}
setprototypeof@1.2.0: {}
@@ -12427,6 +12515,8 @@ snapshots:
glob: 7.2.3
minimatch: 3.1.2
text-encoding@0.7.0: {}
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
@@ -12629,6 +12719,8 @@ snapshots:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-module@2.0.1: {}
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -12637,6 +12729,12 @@ snapshots:
word-wrap@1.2.5: {}
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@@ -12685,6 +12783,8 @@ snapshots:
xmlbuilder@15.1.1: {}
y18n@4.0.3: {}
y18n@5.0.8: {}
yallist@3.1.1: {}
@@ -12698,8 +12798,27 @@ snapshots:
yaml@2.8.1: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs-parser@21.1.1: {}
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
yargs@17.7.2:
dependencies:
cliui: 8.0.1
@@ -1,6 +1,6 @@
import type { MoneyRecordGroup, MoneyRecordItem } from '@/types/money/record'
import React, { useCallback, useState } from 'react'
import { RefreshControl, SectionList, Text, TouchableHighlight, View } from 'react-native'
import { RefreshControl, SectionList, Text, TouchableHighlight, Vibration, View } from 'react-native'
import {
Menu,
MenuOption,
@@ -120,7 +120,6 @@ export default function StaticTransactions({
renderItem={({ item, index }) => {
const category = moneyCategoryStore.getCategoryById(item.category_id)
return (
<Menu
renderer={renderers.Popover}
@@ -135,7 +134,12 @@ export default function StaticTransactions({
onOpen={() => setSelectedId(item.id)}
onClose={() => setSelectedId(null)}
>
<MenuTrigger triggerOnLongPress>
<MenuTrigger
triggerOnLongPress
onPress={() => {
Vibration.vibrate(50)
}}
>
<View
className="flex-row items-center gap-4 py-2 px-5"
style={{
@@ -179,7 +183,6 @@ export default function StaticTransactions({
},
}}
>
<MenuOption
customStyles={{
OptionTouchableComponent: TouchableHighlight,
@@ -1,12 +1,34 @@
import type { AFormRef } from '@/components/common/ao'
import { SquareUserRound } from 'lucide-react-native'
import * as React from 'react'
import { useRef, useState } from 'react'
import { Text, View } from 'react-native'
import { AButton, AForm, AFormItem, AInput, ASegmentedTabs, ATab, LucideIcon } from '@/components/common/ao'
import { FlatList, Pressable, TouchableOpacity, View } from 'react-native'
import QRCode from 'react-native-qrcode-svg'
import { AButton, ADivider, AForm, AFormItem, AInput, LucideIcon } from '@/components/common/ao'
import CustomHeader from '@/components/custom/Header'
import { Badge, BadgeIcon, BadgeText } from '@/components/ui/badge'
import { Divider } from '@/components/ui/divider'
import {
Accordion,
AccordionContent,
AccordionHeader,
AccordionIcon,
AccordionItem,
AccordionTitleText,
AccordionTrigger,
} from '@/components/ui/accordion'
import {
Avatar,
AvatarBadge,
AvatarFallbackText,
AvatarImage,
} from '@/components/ui/avatar'
import { Badge, BadgeText } from '@/components/ui/badge'
import { Heading } from '@/components/ui/heading'
import { HStack } from '@/components/ui/hstack'
import { ChevronDownIcon, ChevronUpIcon } from '@/components/ui/icon'
import { Link, LinkText } from '@/components/ui/link'
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
import { Text } from '@/components/ui/text'
import { VStack } from '@/components/ui/vstack'
export default function PagerWithTab() {
const formRef = useRef<AFormRef>(null)
@@ -14,9 +36,11 @@ export default function PagerWithTab() {
code: '',
})
const [accordion, setAccordion] = useState(['a'])
function EnterSapce() {
return (
<View>
<View className="px-4 items-center">
<AForm ref={formRef} form={form}>
<AFormItem
name="username"
@@ -26,7 +50,7 @@ export default function PagerWithTab() {
</AFormItem>
</AForm>
<AButton>加入空间</AButton>
<AButton className="w-full">加入空间</AButton>
<View
className="flex-row items-center w-full"
style={{
@@ -35,19 +59,27 @@ export default function PagerWithTab() {
}}
>
<View className="flex-1">
<Divider />
<ADivider />
</View>
<Text className="mx-3 text-gray-500 text-sm">或</Text>
<View className="flex-1">
<Divider />
<ADivider />
</View>
</View>
<AButton variant="outline">扫码加入</AButton>
<AButton variant="outline" className="w-full">扫码加入</AButton>
<Link className="mt-4">
<LinkText>返回</LinkText>
</Link>
</View>
)
}
const members = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]
return (
<View className="flex-1">
@@ -79,24 +111,65 @@ export default function PagerWithTab() {
<AButton variant="outline">加入已有空间</AButton>
</View> */}
<View className="p-4">
<View>
<View className="flex flex-row items-center justify-between">
<Text className="font-semibold text-lg">邀请好友加入</Text>
</View>
<View
className="flex-row items-center justify-between px-2 py-2 mt-4 border-round"
style={{
backgroundColor: '#f8f9fa',
{/* <View className="px-4 flex-1">
<Accordion
size="md"
variant="unfilled"
type="multiple"
isCollapsible={true}
isDisabled={false}
value={accordion}
onValueChange={(value) => {
setAccordion(value)
}}
>
<AccordionItem value="a">
<AccordionHeader>
<AccordionTrigger
style={{
paddingLeft: 0,
paddingRight: 0,
}}
>
{({ isExpanded }: { isExpanded: boolean }) => {
return (
<>
<AccordionTitleText className="font-semibold text-lg">
我的邀请码
</AccordionTitleText>
{isExpanded
? (
<AccordionIcon as={ChevronUpIcon} className="ml-3" />
)
: (
<AccordionIcon as={ChevronDownIcon} className="ml-3" />
)}
</>
)
}}
</AccordionTrigger>
</AccordionHeader>
<AccordionContent style={{
marginBottom: 0,
paddingBottom: 0,
}}
>
<Text>123456</Text>
<AButton size="xs">复制代码</AButton>
</View>
</View>
>
<TouchableOpacity className="flex-row items-center justify-center ">
<Text className="font-bold text-3xl tracking-[4px] mr-3">123456</Text>
<LucideIcon icon="Copy" size={16} color="blue" clickable={false} />
</TouchableOpacity>
<View className="mt-8">
<View className="items-center justify-center">
<ADivider direction="horizontal" text="扫描二维码" />
<QRCode
size={120}
value="http://awesome.link.qr"
/>
</View>
</AccordionContent>
</AccordionItem>
</Accordion>
<View className="mt-4 flex-1">
<View className="flex flex-row items-center justify-between">
<Text className="font-semibold text-lg">空间成员</Text>
<Badge size="lg" variant="solid" action="muted">
@@ -104,12 +177,65 @@ export default function PagerWithTab() {
</Badge>
</View>
<View className="mt-2">
<Text>列表</Text>
<View>
<FlatList
data={members}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity>
<HStack space="md" className="my-2 items-center">
<Avatar className="bg-indigo-600">
<AvatarFallbackText className="text-white text-lg">
{item.name}
</AvatarFallbackText>
<AvatarBadge />
</Avatar>
<VStack>
<HStack space="sm">
<Heading size="sm">{item.name}</Heading>
<Badge size="sm" variant="solid" action="muted">
<BadgeText>管理员</BadgeText>
</Badge>
</HStack>
<Text size="sm">Marketing Coordinator</Text>
</VStack>
<View className="ml-auto">
<Menu
placement="top"
offset={5}
trigger={triggerProps => (
<Pressable
{...triggerProps}
style={{
padding: 8,
justifyContent: 'center',
alignItems: 'center',
}}
>
<LucideIcon icon="EllipsisVertical" size={20} clickable={false} />
</Pressable>
)}
>
<MenuItem key="Add account" textValue="Add account">
<MenuItemLabel size="sm">添加为管理员</MenuItemLabel>
</MenuItem>
<MenuItem key="Community" textValue="Community">
<MenuItemLabel size="sm">移出空间</MenuItemLabel>
</MenuItem>
</Menu>
</View>
</HStack>
</TouchableOpacity>
)}
/>
</View>
<AButton size="sm" className="mb-8 mt-4">退出该空间</AButton>
</View>
</View>
</View> */}
<EnterSapce />
</View>
</View>
)
@@ -0,0 +1,74 @@
import type { ViewStyle } from 'react-native'
import React from 'react'
import { Text, View } from 'react-native'
interface DividerProps {
direction?: 'horizontal' | 'vertical'
text?: string | React.ReactNode
textPosition?: 'left' | 'center' | 'right'
style?: ViewStyle
className?: string
}
export default function Divider({
direction = 'horizontal',
text,
textPosition = 'center',
style,
className,
}: DividerProps) {
const isString = typeof text === 'string'
// 垂直分割线
if (direction === 'vertical') {
return (
<View
style={style}
className={`w-px bg-gray-300 mx-2 h-4 ${className ?? ''}`}
/>
)
}
// 水平,无文字
if (!text) {
return (
<View
style={style}
className={`h-px bg-gray-300 my-2 w-full ${className ?? ''}`}
/>
)
}
// 水平 + 文本
return (
<View
style={style}
className={`flex-row items-center my-2 ${className ?? ''}`}
>
{/* 左线 */}
<View
className={`h-px bg-gray-300 ${
textPosition === 'left' ? 'w-2' : 'flex-1'
}`}
/>
{/* 文本(字符串 → 自动包裹 Text;node → 直接渲染) */}
<View className="mx-3">
{isString
? (
<Text className="text-gray-500 text-sm">{text}</Text>
)
: (
text
)}
</View>
{/* 右线 */}
<View
className={`h-px bg-gray-300 ${
textPosition === 'right' ? 'w-2' : 'flex-1'
}`}
/>
</View>
)
}
@@ -2,6 +2,7 @@ import Button from './Button'
import { Cell, CellGroup } from './Cell'
import Checkbox from './Checkbox'
import DatePicker from './DatePicker'
import Divider from './Divider'
import Form, { FormRef } from './Form'
import FormItem from './FormItem'
import Input from './Input'
@@ -16,6 +17,7 @@ export {
CellGroup as ACellGroup,
Checkbox as ACheckbox,
DatePicker as ADatePicker,
Divider as ADivider,
Form as AForm,
FormItem as AFormItem,
FormRef as AFormRef,
@@ -0,0 +1,331 @@
'use client';
import React from 'react';
import { createAccordion } from '@gluestack-ui/core/accordion/creator';
import { View, Pressable, Text, Platform, TextProps } from 'react-native';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import {
withStyleContext,
useStyleContext,
} from '@gluestack-ui/utils/nativewind-utils';
import { H3 } from '@expo/html-elements';
import { cssInterop } from 'nativewind';
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
const SCOPE = 'ACCORDION';
/** Styles */
const accordionStyle = tva({
base: 'w-full',
variants: {
variant: {
filled: 'bg-white shadow-hard-2',
unfilled: '',
},
size: {
sm: '',
md: '',
lg: '',
},
},
});
const accordionItemStyle = tva({
base: '',
parentVariants: {
variant: {
filled: 'bg-background-0',
unfilled: 'bg-transparent',
},
},
});
const accordionTitleTextStyle = tva({
base: 'text-typography-900 font-bold flex-1 text-left',
parentVariants: {
size: {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
},
},
});
const accordionIconStyle = tva({
base: 'text-typography-900 fill-none',
parentVariants: {
size: {
'2xs': 'h-3 w-3',
'xs': 'h-3.5 w-3.5',
'sm': 'h-4 w-4',
'md': 'h-[18px] w-[18px]',
'lg': 'h-5 w-5',
'xl': 'h-6 w-6',
},
},
});
const accordionContentTextStyle = tva({
base: 'text-typography-700 font-normal',
parentVariants: {
size: {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
},
},
});
const accordionHeaderStyle = tva({
base: 'mx-0 my-0',
});
const accordionContentStyle = tva({
base: 'pt-1 pb-3 px-4',
});
const accordionTriggerStyle = tva({
base: 'w-full flex-row justify-between items-center web:outline-none focus:outline-none data-[disabled=true]:opacity-40 data-[disabled=true]:cursor-not-allowed data-[focus-visible=true]:bg-background-50 py-3 px-4',
});
const Root = withStyleContext(View, SCOPE);
const Header = (
Platform.OS === 'web' ? H3 : View
) as React.ComponentType<TextProps>;
/** Creator */
const UIAccordion = createAccordion({
Root: Root,
Item: View,
Header: Header,
Trigger: Pressable,
Icon: UIIcon,
TitleText: Text,
ContentText: Text,
Content: View,
});
cssInterop(PrimitiveIcon, {
className: {
target: 'style',
nativeStyleToProp: {
height: true,
width: true,
fill: true,
color: 'classNameColor',
stroke: true,
},
},
});
cssInterop(H3, {
className: {
target: 'style',
},
});
type IAccordionProps = React.ComponentPropsWithoutRef<typeof UIAccordion> &
VariantProps<typeof accordionStyle>;
type IAccordionItemProps = React.ComponentPropsWithoutRef<
typeof UIAccordion.Item
> &
VariantProps<typeof accordionItemStyle>;
type IAccordionContentProps = React.ComponentPropsWithoutRef<
typeof UIAccordion.Content
> &
VariantProps<typeof accordionContentStyle>;
type IAccordionContentTextProps = React.ComponentPropsWithoutRef<
typeof UIAccordion.ContentText
> &
VariantProps<typeof accordionContentTextStyle>;
type IAccordionIconProps = VariantProps<typeof accordionIconStyle> &
React.ComponentPropsWithoutRef<typeof UIAccordion.Icon> & {
as?: React.ElementType;
height?: number;
width?: number;
};
type IAccordionHeaderProps = React.ComponentPropsWithoutRef<
typeof UIAccordion.Header
> &
VariantProps<typeof accordionHeaderStyle>;
type IAccordionTriggerProps = React.ComponentPropsWithoutRef<
typeof UIAccordion.Trigger
> &
VariantProps<typeof accordionTriggerStyle>;
type IAccordionTitleTextProps = React.ComponentPropsWithoutRef<
typeof UIAccordion.TitleText
> &
VariantProps<typeof accordionTitleTextStyle>;
/** Components */
const Accordion = React.forwardRef<
React.ComponentRef<typeof UIAccordion>,
IAccordionProps
>(({ className, variant = 'filled', size = 'md', ...props }, ref) => {
return (
<UIAccordion
ref={ref}
{...props}
className={accordionStyle({ variant, class: className })}
context={{ variant, size }}
/>
);
});
const AccordionItem = React.forwardRef<
React.ComponentRef<typeof UIAccordion.Item>,
IAccordionItemProps
>(({ className, ...props }, ref) => {
const { variant } = useStyleContext(SCOPE);
return (
<UIAccordion.Item
ref={ref}
{...props}
className={accordionItemStyle({
parentVariants: { variant },
class: className,
})}
/>
);
});
const AccordionContent = React.forwardRef<
React.ComponentRef<typeof UIAccordion.Content>,
IAccordionContentProps
>(function AccordionContent({ className, ...props }, ref) {
return (
<UIAccordion.Content
ref={ref}
{...props}
className={accordionContentStyle({
class: className,
})}
/>
);
});
const AccordionContentText = React.forwardRef<
React.ComponentRef<typeof UIAccordion.ContentText>,
IAccordionContentTextProps
>(function AccordionContentText({ className, ...props }, ref) {
const { size } = useStyleContext(SCOPE);
return (
<UIAccordion.ContentText
ref={ref}
{...props}
className={accordionContentTextStyle({
parentVariants: { size },
class: className,
})}
/>
);
});
const AccordionIcon = React.forwardRef<
React.ComponentRef<typeof UIAccordion.Icon>,
IAccordionIconProps
>(function AccordionIcon({ size, className, ...props }, ref) {
const { size: parentSize } = useStyleContext(SCOPE);
if (typeof size === 'number') {
return (
<UIAccordion.Icon
ref={ref}
{...props}
className={accordionIconStyle({ class: className })}
size={size}
/>
);
} else if (
(props.height !== undefined || props.width !== undefined) &&
size === undefined
) {
return (
<UIAccordion.Icon
ref={ref}
{...props}
className={accordionIconStyle({ class: className })}
/>
);
}
return (
<UIAccordion.Icon
ref={ref}
{...props}
className={accordionIconStyle({
size,
class: className,
parentVariants: { size: parentSize },
})}
/>
);
});
const AccordionHeader = React.forwardRef<
React.ComponentRef<typeof UIAccordion.Header>,
IAccordionHeaderProps
>(function AccordionHeader({ className, ...props }, ref) {
return (
<UIAccordion.Header
ref={ref}
{...props}
className={accordionHeaderStyle({
class: className,
})}
/>
);
});
const AccordionTrigger = React.forwardRef<
React.ComponentRef<typeof UIAccordion.Trigger>,
IAccordionTriggerProps
>(function AccordionTrigger({ className, ...props }, ref) {
return (
<UIAccordion.Trigger
ref={ref}
{...props}
className={accordionTriggerStyle({
class: className,
})}
/>
);
});
const AccordionTitleText = React.forwardRef<
React.ComponentRef<typeof UIAccordion.TitleText>,
IAccordionTitleTextProps
>(function AccordionTitleText({ className, ...props }, ref) {
const { size } = useStyleContext(SCOPE);
return (
<UIAccordion.TitleText
ref={ref}
{...props}
className={accordionTitleTextStyle({
parentVariants: { size },
class: className,
})}
/>
);
});
Accordion.displayName = 'Accordion';
AccordionItem.displayName = 'AccordionItem';
AccordionHeader.displayName = 'AccordionHeader';
AccordionTrigger.displayName = 'AccordionTrigger';
AccordionTitleText.displayName = 'AccordionTitleText';
AccordionContentText.displayName = 'AccordionContentText';
AccordionIcon.displayName = 'AccordionIcon';
AccordionContent.displayName = 'AccordionContent';
export {
Accordion,
AccordionItem,
AccordionHeader,
AccordionTrigger,
AccordionTitleText,
AccordionContentText,
AccordionIcon,
AccordionContent,
};
@@ -0,0 +1,220 @@
import React, { forwardRef, memo } from 'react';
import { H1, H2, H3, H4, H5, H6 } from '@expo/html-elements';
import { headingStyle } from './styles';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { cssInterop } from 'nativewind';
type IHeadingProps = VariantProps<typeof headingStyle> &
React.ComponentPropsWithoutRef<typeof H1> & {
as?: React.ElementType;
};
cssInterop(H1, { className: 'style' });
cssInterop(H2, { className: 'style' });
cssInterop(H3, { className: 'style' });
cssInterop(H4, { className: 'style' });
cssInterop(H5, { className: 'style' });
cssInterop(H6, { className: 'style' });
const MappedHeading = memo(
forwardRef<React.ComponentRef<typeof H1>, IHeadingProps>(
function MappedHeading(
{
size,
className,
isTruncated,
bold,
underline,
strikeThrough,
sub,
italic,
highlight,
...props
},
ref
) {
switch (size) {
case '5xl':
case '4xl':
case '3xl':
return (
<H1
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
case '2xl':
return (
<H2
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
case 'xl':
return (
<H3
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
case 'lg':
return (
<H4
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
case 'md':
return (
<H5
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
case 'sm':
case 'xs':
return (
<H6
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
default:
return (
<H4
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
// @ts-expect-error : type issue
ref={ref}
/>
);
}
}
)
);
const Heading = memo(
forwardRef<React.ComponentRef<typeof H1>, IHeadingProps>(function Heading(
{ className, size = 'lg', as: AsComp, ...props },
ref
) {
const {
isTruncated,
bold,
underline,
strikeThrough,
sub,
italic,
highlight,
} = props;
if (AsComp) {
return (
<AsComp
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
/>
);
}
return (
<MappedHeading className={className} size={size} ref={ref} {...props} />
);
})
);
Heading.displayName = 'Heading';
export { Heading };
@@ -0,0 +1,202 @@
import React, { forwardRef, memo } from 'react';
import { headingStyle } from './styles';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
type IHeadingProps = VariantProps<typeof headingStyle> &
React.ComponentPropsWithoutRef<'h1'> & {
as?: React.ElementType;
};
const MappedHeading = memo(
forwardRef<HTMLHeadingElement, IHeadingProps>(function MappedHeading(
{
size,
className,
isTruncated,
bold,
underline,
strikeThrough,
sub,
italic,
highlight,
...props
},
ref
) {
switch (size) {
case '5xl':
case '4xl':
case '3xl':
return (
<h1
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
case '2xl':
return (
<h2
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
case 'xl':
return (
<h3
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
case 'lg':
return (
<h4
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
case 'md':
return (
<h5
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
case 'sm':
case 'xs':
return (
<h6
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
default:
return (
<h4
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
})
);
const Heading = memo(
forwardRef<HTMLHeadingElement, IHeadingProps>(function Heading(
{ className, size = 'lg', as: AsComp, ...props },
ref
) {
const {
isTruncated,
bold,
underline,
strikeThrough,
sub,
italic,
highlight,
} = props;
if (AsComp) {
return (
<AsComp
className={headingStyle({
size,
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
return (
<MappedHeading className={className} size={size} ref={ref} {...props} />
);
})
);
Heading.displayName = 'Heading';
export { Heading };
@@ -0,0 +1,43 @@
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
const baseStyle = isWeb
? 'font-sans tracking-sm bg-transparent border-0 box-border display-inline list-none margin-0 padding-0 position-relative text-start no-underline whitespace-pre-wrap word-wrap-break-word'
: '';
export const headingStyle = tva({
base: `text-typography-900 font-bold font-heading tracking-sm my-0 ${baseStyle}`,
variants: {
isTruncated: {
true: 'truncate',
},
bold: {
true: 'font-bold',
},
underline: {
true: 'underline',
},
strikeThrough: {
true: 'line-through',
},
sub: {
true: 'text-xs',
},
italic: {
true: 'italic',
},
highlight: {
true: 'bg-yellow-500',
},
size: {
'5xl': 'text-6xl',
'4xl': 'text-5xl',
'3xl': 'text-4xl',
'2xl': 'text-3xl',
'xl': 'text-2xl',
'lg': 'text-xl',
'md': 'text-lg',
'sm': 'text-base',
'xs': 'text-sm',
},
},
});
@@ -0,0 +1,27 @@
import React from 'react';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { View } from 'react-native';
import type { ViewProps } from 'react-native';
import { hstackStyle } from './styles';
type IHStackProps = ViewProps & VariantProps<typeof hstackStyle>;
const HStack = React.forwardRef<React.ComponentRef<typeof View>, IHStackProps>(
function HStack({ className, space, reversed, ...props }, ref) {
return (
<View
className={hstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
HStack.displayName = 'HStack';
export { HStack };
@@ -0,0 +1,26 @@
import React from 'react';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { hstackStyle } from './styles';
type IHStackProps = React.ComponentPropsWithoutRef<'div'> &
VariantProps<typeof hstackStyle>;
const HStack = React.forwardRef<React.ComponentRef<'div'>, IHStackProps>(
function HStack({ className, space, reversed, ...props }, ref) {
return (
<div
className={hstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
HStack.displayName = 'HStack';
export { HStack };
@@ -0,0 +1,25 @@
import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
const baseStyle = isWeb
? 'flex relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none'
: '';
export const hstackStyle = tva({
base: `flex-row ${baseStyle}`,
variants: {
space: {
'xs': 'gap-1',
'sm': 'gap-2',
'md': 'gap-3',
'lg': 'gap-4',
'xl': 'gap-5',
'2xl': 'gap-6',
'3xl': 'gap-7',
'4xl': 'gap-8',
},
reversed: {
true: 'flex-row-reverse',
},
},
});
@@ -0,0 +1,48 @@
import React from 'react';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { Text as RNText } from 'react-native';
import { textStyle } from './styles';
type ITextProps = React.ComponentProps<typeof RNText> &
VariantProps<typeof textStyle>;
const Text = React.forwardRef<React.ComponentRef<typeof RNText>, ITextProps>(
function Text(
{
className,
isTruncated,
bold,
underline,
strikeThrough,
size = 'md',
sub,
italic,
highlight,
...props
},
ref
) {
return (
<RNText
className={textStyle({
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
size,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
Text.displayName = 'Text';
export { Text };
@@ -0,0 +1,45 @@
import React from 'react';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { textStyle } from './styles';
type ITextProps = React.ComponentProps<'span'> & VariantProps<typeof textStyle>;
const Text = React.forwardRef<React.ComponentRef<'span'>, ITextProps>(
function Text(
{
className,
isTruncated,
bold,
underline,
strikeThrough,
size = 'md',
sub,
italic,
highlight,
...props
}: { className?: string } & ITextProps,
ref
) {
return (
<span
className={textStyle({
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
size,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
Text.displayName = 'Text';
export { Text };
@@ -0,0 +1,47 @@
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
const baseStyle = isWeb
? 'font-sans tracking-sm my-0 bg-transparent border-0 box-border display-inline list-none margin-0 padding-0 position-relative text-start no-underline whitespace-pre-wrap word-wrap-break-word'
: '';
export const textStyle = tva({
base: `text-typography-700 font-body ${baseStyle}`,
variants: {
isTruncated: {
true: 'web:truncate',
},
bold: {
true: 'font-bold',
},
underline: {
true: 'underline',
},
strikeThrough: {
true: 'line-through',
},
size: {
'2xs': 'text-2xs',
'xs': 'text-xs',
'sm': 'text-sm',
'md': 'text-base',
'lg': 'text-lg',
'xl': 'text-xl',
'2xl': 'text-2xl',
'3xl': 'text-3xl',
'4xl': 'text-4xl',
'5xl': 'text-5xl',
'6xl': 'text-6xl',
},
sub: {
true: 'text-xs',
},
italic: {
true: 'italic',
},
highlight: {
true: 'bg-yellow-500',
},
},
});
@@ -0,0 +1,28 @@
import React from 'react';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { View } from 'react-native';
import { vstackStyle } from './styles';
type IVStackProps = React.ComponentProps<typeof View> &
VariantProps<typeof vstackStyle>;
const VStack = React.forwardRef<React.ComponentRef<typeof View>, IVStackProps>(
function VStack({ className, space, reversed, ...props }, ref) {
return (
<View
className={vstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
VStack.displayName = 'VStack';
export { VStack };
@@ -0,0 +1,27 @@
import React from 'react';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { vstackStyle } from './styles';
type IVStackProps = React.ComponentProps<'div'> &
VariantProps<typeof vstackStyle>;
const VStack = React.forwardRef<React.ComponentRef<'div'>, IVStackProps>(
function VStack({ className, space, reversed, ...props }, ref) {
return (
<div
className={vstackStyle({
space,
reversed: reversed as boolean,
class: className,
})}
{...props}
ref={ref}
/>
);
}
);
VStack.displayName = 'VStack';
export { VStack };
@@ -0,0 +1,25 @@
import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
const baseStyle = isWeb
? 'flex flex-col relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none'
: '';
export const vstackStyle = tva({
base: `flex-col ${baseStyle}`,
variants: {
space: {
'xs': 'gap-1',
'sm': 'gap-2',
'md': 'gap-3',
'lg': 'gap-4',
'xl': 'gap-5',
'2xl': 'gap-6',
'3xl': 'gap-7',
'4xl': 'gap-8',
},
reversed: {
true: 'flex-col-reverse',
},
},
});
@@ -32,8 +32,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.136:8086/app',
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.136:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
+120 -429
View File
@@ -1,457 +1,148 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>查询进度步骤条 - 精确对齐版本</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
<meta charset="UTF-8" />
<title>高级邀请页面</title>
<style>
body {
margin: 0;
font-family: "Inter", sans-serif;
background: #f3f4f7;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 40px 20px;
}
/* 顶部渐变 Header */
.header {
width: 100%;
padding: 40px 24px;
background: linear-gradient(135deg, #4d8dff, #6fa5ff);
color: white;
font-size: 26px;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.g3-main-content-indicator{
display: block;
}
/* 内容卡片 */
.container {
margin-top: -32px;
padding: 0 24px 40px;
}
.g3-main-content-indicator-title {
font-size: 22px;
font-weight: 600;
margin-bottom: 15px;
color: #1a2980;
}
.card {
background: white;
padding: 24px;
border-radius: 20px;
box-shadow: 0 6px 20px rgba(0,0,0,0.06);
margin-bottom: 24px;
}
.progress-steps {
display: flex;
justify-content: space-between;
position: relative;
margin: 60px 0px;
counter-reset: step;
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.step {
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.title {
font-size: 20px;
font-weight: 600;
}
.step-connector {
position: absolute;
top: 28px;
height: 4px;
background-color: #e0e0e0;
z-index: 1;
}
.invite-code {
font-size: 40px;
font-weight: 700;
letter-spacing: 4px;
color: #333;
text-align: center;
margin-top: 12px;
}
.step-progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0;
background: #4b6cb7;
transition: width 0.8s ease, background-color 0.8s ease;
z-index: 2;
}
.copy-btn {
display: flex;
justify-content: center;
margin-top: 8px;
font-size: 14px;
color: #4d8dff;
cursor: pointer;
}
.step-progress.success {
background: linear-gradient(to right, #4CAF50, #2E7D32);
}
/* 二维码卡片设计 */
.qr-card {
background: white;
padding: 20px;
border-radius: 20px;
text-align: center;
box-shadow: 0 4px 14px rgba(0,0,0,0.05);
margin-bottom: 24px;
}
.step-progress.failed {
background: linear-gradient(to right, #f44336, #c62828);
}
.qr-img {
width: 190px;
height: 190px;
border-radius: 12px;
border: 1px solid #eee;
}
.step-icon {
width: 50px;
height: 50px;
border-radius: 50%;
background: #e0e0e0;
display: flex;
justify-content: center;
align-items: center;
color: #999;
font-weight: bold;
border: 4px solid white;
box-shadow: 0 0 0 3px #e0e0e0;
transition: all 0.5s ease;
position: relative;
z-index: 4;
font-size: 24px;
}
.badge {
background: #eef1ff;
padding: 6px 14px;
border-radius: 14px;
font-size: 14px;
color: #4d5bff;
}
.step.active .step-icon {
background: #4b6cb7;
color: white;
box-shadow: 0 0 0 3px #b8c6f0;
animation: pulse 1.5s infinite;
}
.member-item {
padding: 14px 0;
border-bottom: 1px solid #f0f0f0;
font-size: 16px;
}
.step.completed .step-icon {
background: #4CAF50;
color: white;
box-shadow: 0 0 0 3px #a5d6a7;
}
.step.failed .step-icon {
background: #f44336;
color: white;
box-shadow: 0 0 0 3px #ffcdd2;
}
.step.completed .step-icon::after {
content: '✓';
font-size: 24px;
}
.step.failed .step-icon::after {
content: '✕';
font-size: 24px;
}
.step-label {
margin-top: 10px;
text-align: center;
font-size: 14px;
color: #666;
font-weight: 500;
max-width: 100px;
}
.step.active .step-label {
color: #4b6cb7;
font-weight: 600;
}
.step.completed .step-label {
color: #4CAF50;
}
.step.failed .step-label {
color: #f44336;
font-weight: 600;
}
.progress-info {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
text-align: center;
border-left: 4px solid #4b6cb7;
display: flex;
flex-direction: column;
justify-content: center;
}
.progress-info.failed {
border-left-color: #f44336;
}
.progress-info.success {
border-left-color: #4CAF50;
}
.progress-status {
font-size: 16px;
color: #333;
margin-bottom: 10px;
}
.progress-details {
font-size: 18px;
line-height: 1.5;
}
.progress-time {
display: inline-block;
background: #e3f2fd;
padding: 3px 8px;
border-radius: 4px;
font-size: 16px;
color: #1565c0;
margin-top: 5px;
}
</style>
.member-item:last-child {
border-bottom: none;
}
</style>
</head>
<body>
<div id="statusIndicator" class="g3-main-content-indicator">
<div class="g3-main-content-indicator-title">查询状态</div>
<div class="progress-steps">
<!-- 步骤1 -->
<div class="step active" id="step1">
<div class="step-icon">1</div>
<div class="step-label">验证查询条件</div>
</div>
<!-- 步骤2 -->
<div class="step" id="step2">
<div class="step-icon">2</div>
<div class="step-label">发出请求</div>
</div>
<!-- Header -->
<div class="header">邀请好友加入空间</div>
<!-- 步骤3 -->
<div class="step" id="step3">
<div class="step-icon">3</div>
<div class="step-label">等待响应</div>
</div>
<!-- 内容 -->
<div class="container">
<!-- 步骤4 -->
<div class="step" id="step4">
<div class="step-icon">4</div>
<div class="step-label">查询成功</div>
</div>
<!-- 连接线1-2 -->
<div class="step-connector" id="connector1" style="left: 12.5%; width: 25%;">
<div class="step-progress" id="progress1"></div>
</div>
<!-- 连接线2-3 -->
<div class="step-connector" id="connector2" style="left: 37.5%; width: 25%;">
<div class="step-progress" id="progress2"></div>
</div>
<!-- 连接线3-4 -->
<div class="step-connector" id="connector3" style="left: 62.5%; width: 25%;">
<div class="step-progress" id="progress3"></div>
</div>
</div>
<div class="progress-info" id="progressInfo">
<div class="progress-details" id="progressDetails">
检查查询参数的有效性,确保数据请求符合系统要求。
<div class="progress-time">已用时间: <span id="timeElapsed">0分00秒</span></div>
</div>
</div>
<!-- 邀请码卡片 -->
<div class="card">
<div class="title-row">
<div class="title">我的邀请码</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const steps = document.querySelectorAll('.step');
const stepProgresses = document.querySelectorAll('.step-progress');
const connectors = document.querySelectorAll('.step-connector');
const progressInfoElement = document.getElementById('progressInfo');
const progressDetails = document.getElementById('progressDetails');
const timeElapsed = document.getElementById('timeElapsed');
let currentStep = 1;
let isRunning = false;
let hasFailed = false;
let timerInterval;
let seconds = 0;
let stepStatus = [null, 'active', null, null, null];
<div class="invite-code">123456</div>
<div class="copy-btn">点击复制邀请码</div>
</div>
<!-- 二维码卡片 -->
<div class="qr-card">
<img
src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=demo"
class="qr-img"
alt="二维码"
/>
<p style="margin-top: 12px; color: #666; font-size: 14px;">
扫码加入我的共享空间
</p>
</div>
// 计算连接线位置
function calculateConnectorPositions() {
const stepWidth = 100 / steps.length; // 每个步骤占据的宽度百分比
<!-- 成员卡片 -->
<div class="card">
<div class="title-row">
<div class="title">空间成员</div>
<div class="badge">3 人</div>
</div>
connectors.forEach((connector, index) => {
// 计算连接线的起始位置和宽度
const left = (index + 0.5) * stepWidth; // 从第一个步骤的中心开始
const width = stepWidth; // 连接线宽度等于步骤宽度
<div class="member-item">成员 A</div>
<div class="member-item">成员 B</div>
<div class="member-item">成员 C</div>
</div>
connector.style.left = left + `%`;
connector.style.width = width + `%`;
});
}
</div>
// 更新步骤状态
function updateSteps() {
steps.forEach((step, index) => {
step.classList.remove('active', 'completed', 'failed');
if (index + 1 < currentStep) {
step.classList.add('completed');
} else if (index + 1 === currentStep) {
if (currentStep > steps.length) {
// 如果所有步骤已完成,最后一个步骤显示为完成状态
step.classList.add('completed');
} else {
step.classList.add('active');
}
}
});
}
// 更新进度条
function updateProgressBars() {
// 重置所有进度条
stepProgresses.forEach((progress, index) => {
progress.style.width = '0';
progress.classList.remove('success', 'failed');
});
// 设置已完成的进度条
for (let i = 1; i < currentStep; i++) {
if (i <= stepProgresses.length) {
const progress = document.getElementById(`progress` + i);
if (progress) {
progress.style.width = '100%';
// 根据步骤状态设置颜色
if (stepStatus[i] === 'completed') {
progress.classList.add('success');
} else if (stepStatus[i] === 'failed') {
progress.classList.add('failed');
}
}
}
}
}
// 标记步骤失败
function markStepFailed(stepIndex, error) {
stepStatus[stepIndex] = 'failed';
hasFailed = true;
steps[stepIndex - 1].classList.remove('active', 'completed');
steps[stepIndex - 1].classList.add('failed');
// 标记前一个连接线为失败
if (stepIndex > 1) {
const prevProgress = document.getElementById("progress" + (stepIndex - 1));
if (prevProgress) {
prevProgress.classList.add('failed');
}
}
progressInfoElement.classList.add('failed');
isRunning = false;
stopTimer();
updateProgressBars();
}
// 标记步骤成功
function markStepCompleted(stepIndex) {
stepStatus[stepIndex] = 'completed';
updateSteps();
// 标记前一个连接线为成功
if (stepIndex > 1) {
const prevProgress = document.getElementById("progress" + (stepIndex - 1));
if (prevProgress) {
prevProgress.classList.add('success');
}
}
updateProgressBars();
}
// 标记所有步骤完成
function markAllStepsCompleted() {
for (let i = 1; i <= steps.length; i++) {
stepStatus[i] = 'completed';
}
updateSteps();
updateProgressBars();
}
// 更新状态信息
function updateStatus() {
const statusTexts = [
'',
'正在验证查询条件...',
'正在向服务器发出请求...',
'正在等待服务器响应...',
'查询成功,正在处理结果...'
];
const detailTexts = [
'',
'检查查询参数的有效性,确保数据请求符合系统要求。',
'将查询请求发送到远程服务器,建立数据连接。',
'等待服务器处理查询请求并返回结果。',
'处理查询结果并准备返回,即将完成整个过程。'
];
progressDetails.innerHTML = detailTexts[currentStep] +
'<div class="progress-time">已用时间: <span id="timeElapsed">'
+ formatTime(seconds) +
'</span></div>';
progressInfoElement.classList.remove('failed');
}
// 格式化时间显示
function formatTime(totalSeconds) {
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return minutes + "分" + seconds.toString().padStart(2, "0") + "秒";
}
// 开始计时器
function startTimer() {
seconds = 0;
timerInterval = setInterval(() => {
seconds++;
timeElapsed.textContent = formatTime(seconds);
}, 1000);
}
// 停止计时器
function stopTimer() {
clearInterval(timerInterval);
}
// 初始化
updateSteps();
updateProgressBars();
updateStatus();
function runStep() {
if (currentStep > steps.length) {
// 查询完成
isRunning = false;
stopTimer();
markAllStepsCompleted();
progressDetails.innerHTML =
'数据查询已成功完成,结果已准备就绪。' +
'<div class="progress-time">总用时: <span id="timeElapsed">'
+ formatTime(seconds) +
'</span></div>';
return;
}
updateStatus();
updateSteps();
updateProgressBars();
const stepWillFail = Math.random() < 0.2;
const duration = [0,1000,1000,1000,1000][currentStep];
setTimeout(() => {
if (stepWillFail && !hasFailed) {
markStepFailed(currentStep, "模拟失败");
hasFailed = true;
return;
}
markStepCompleted(currentStep);
currentStep++;
runStep(); // ✅ 递归调用下一步
}, duration);
}
runStep();
})
</script>
</body>
</html>