u
This commit is contained in:
1 parent
d6dd5b032d
commit
8a6d29f5d0
16 files changed
+514
-69
No files matched your search
@@ -19,8 +19,10 @@
|
|||||||
"alova": "^3.3.4",
|
"alova": "^3.3.4",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"expo": "~54.0.20",
|
"expo": "~54.0.20",
|
||||||
|
"expo-blur": "~15.0.7",
|
||||||
"expo-constants": "~18.0.10",
|
"expo-constants": "~18.0.10",
|
||||||
"expo-font": "~14.0.9",
|
"expo-font": "~14.0.9",
|
||||||
|
"expo-linear-gradient": "~15.0.7",
|
||||||
"expo-linking": "~8.0.8",
|
"expo-linking": "~8.0.8",
|
||||||
"expo-router": "~6.0.14",
|
"expo-router": "~6.0.14",
|
||||||
"expo-splash-screen": "~31.0.10",
|
"expo-splash-screen": "~31.0.10",
|
||||||
|
|||||||
+40
@@ -35,12 +35,18 @@ importers:
|
|||||||
expo:
|
expo:
|
||||||
specifier: ~54.0.20
|
specifier: ~54.0.20
|
||||||
version: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
expo-blur:
|
||||||
|
specifier: ~15.0.7
|
||||||
|
version: 15.0.7(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
expo-constants:
|
expo-constants:
|
||||||
specifier: ~18.0.10
|
specifier: ~18.0.10
|
||||||
version: 18.0.10(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
version: 18.0.10(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||||
expo-font:
|
expo-font:
|
||||||
specifier: ~14.0.9
|
specifier: ~14.0.9
|
||||||
version: 14.0.9(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 14.0.9(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
expo-linear-gradient:
|
||||||
|
specifier: ~15.0.7
|
||||||
|
version: 15.0.7(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
expo-linking:
|
expo-linking:
|
||||||
specifier: ~8.0.8
|
specifier: ~8.0.8
|
||||||
version: 8.0.8(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 8.0.8(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -3012,6 +3018,13 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
|
expo-blur@15.0.7:
|
||||||
|
resolution: {integrity: sha512-SugQQbQd+zRPy8z2G5qDD4NqhcD7srBF7fN7O7yq6q7ZFK59VWvpDxtMoUkmSfdxgqONsrBN/rLdk00USADrMg==}
|
||||||
|
peerDependencies:
|
||||||
|
expo: '*'
|
||||||
|
react: '*'
|
||||||
|
react-native: '*'
|
||||||
|
|
||||||
expo-constants@18.0.10:
|
expo-constants@18.0.10:
|
||||||
resolution: {integrity: sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw==}
|
resolution: {integrity: sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3037,6 +3050,13 @@ packages:
|
|||||||
expo: '*'
|
expo: '*'
|
||||||
react: '*'
|
react: '*'
|
||||||
|
|
||||||
|
expo-linear-gradient@15.0.7:
|
||||||
|
resolution: {integrity: sha512-yF+y+9Shpr/OQFfy/wglB/0bykFMbwHBTuMRa5Of/r2P1wbkcacx8rg0JsUWkXH/rn2i2iWdubyqlxSJa3ggZA==}
|
||||||
|
peerDependencies:
|
||||||
|
expo: '*'
|
||||||
|
react: '*'
|
||||||
|
react-native: '*'
|
||||||
|
|
||||||
expo-linking@8.0.8:
|
expo-linking@8.0.8:
|
||||||
resolution: {integrity: sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==}
|
resolution: {integrity: sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3670,48 +3690,56 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-gnu@1.30.2:
|
lightningcss-linux-arm64-gnu@1.30.2:
|
||||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.27.0:
|
lightningcss-linux-arm64-musl@1.27.0:
|
||||||
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.30.2:
|
lightningcss-linux-arm64-musl@1.30.2:
|
||||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.27.0:
|
lightningcss-linux-x64-gnu@1.27.0:
|
||||||
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.30.2:
|
lightningcss-linux-x64-gnu@1.30.2:
|
||||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.27.0:
|
lightningcss-linux-x64-musl@1.27.0:
|
||||||
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.30.2:
|
lightningcss-linux-x64-musl@1.30.2:
|
||||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.27.0:
|
lightningcss-win32-arm64-msvc@1.27.0:
|
||||||
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
||||||
@@ -9246,6 +9274,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
expo-blur@15.0.7(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
expo: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
|
||||||
expo-constants@18.0.10(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
|
expo-constants@18.0.10(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@expo/config': 12.0.10
|
'@expo/config': 12.0.10
|
||||||
@@ -9272,6 +9306,12 @@ snapshots:
|
|||||||
expo: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
expo: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
expo-linear-gradient@15.0.7(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
expo: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
|
||||||
expo-linking@8.0.8(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
expo-linking@8.0.8(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
expo-constants: 18.0.10(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
expo-constants: 18.0.10(expo@54.0.21)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||||
|
|||||||
@@ -2,19 +2,22 @@ import type { TabbarItem } from '@/components/custom/Tabbar'
|
|||||||
import CustomTabbar from '@/components/custom/Tabbar'
|
import CustomTabbar from '@/components/custom/Tabbar'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
|
export const unstable_settings = {
|
||||||
|
initialRouteName: 'money',
|
||||||
|
}
|
||||||
|
|
||||||
export default function TabsLayout() {
|
export default function TabsLayout() {
|
||||||
const tabbarOptions: TabbarItem[] = [
|
const tabbarOptions: TabbarItem[] = [
|
||||||
{
|
|
||||||
name: 'user',
|
|
||||||
href: '/user',
|
|
||||||
icon: 'CircleCheckBig',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'money',
|
name: 'money',
|
||||||
href: '/money',
|
href: '/money',
|
||||||
icon: 'CircleDollarSign',
|
icon: 'CircleDollarSign',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'user',
|
||||||
|
href: '/user',
|
||||||
|
icon: 'CircleCheckBig',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'app',
|
name: 'app',
|
||||||
icon: 'LayoutGrid',
|
icon: 'LayoutGrid',
|
||||||
@@ -37,6 +40,7 @@ export default function TabsLayout() {
|
|||||||
icon: 'Heart',
|
icon: 'Heart',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomTabbar data={tabbarOptions} />
|
<CustomTabbar data={tabbarOptions} />
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,53 @@
|
|||||||
import { Text, View } from 'react-native'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { Pressable, Text, View } from 'react-native'
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
|
||||||
export default function Money() {
|
export default function Money() {
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
const [active, setActive] = useState<'明细' | '视图'>('明细')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<LinearGradient
|
||||||
<Text>Money</Text>
|
colors={['#fffaf0', '#fefefe', '#f0fff4']}
|
||||||
</View>
|
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>
|
||||||
|
</LinearGradient>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,83 @@
|
|||||||
import { Text, View } from 'react-native'
|
import { router } from 'expo-router'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { AButton } from '@/components/common/ao'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
|
||||||
export default function User() {
|
export default function User() {
|
||||||
|
const userInfo = useUserStore(state => state.userInfo)
|
||||||
|
|
||||||
|
if (!userInfo) {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.empty}>未登录</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const genderText = userInfo.gender === 1 ? '男' : userInfo.gender === 2 ? '女' : '未设置'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={styles.container}>
|
||||||
<Text>User</Text>
|
<Text style={styles.title}>个人信息</Text>
|
||||||
|
<View style={styles.infoBox}>
|
||||||
|
<Text style={styles.item}>
|
||||||
|
账号:
|
||||||
|
{userInfo.account}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.item}>
|
||||||
|
用户名:
|
||||||
|
{userInfo.username}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.item}>
|
||||||
|
ID:
|
||||||
|
{userInfo.id}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.item}>
|
||||||
|
序号:
|
||||||
|
{userInfo.seqNo}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.item}>
|
||||||
|
性别:
|
||||||
|
{genderText}
|
||||||
|
</Text>
|
||||||
|
{userInfo.rememberMe && <Text style={styles.item}>已开启记住我功能</Text>}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<AButton onPress={() => {
|
||||||
|
router.replace('/auth')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
跳转到登陆页面
|
||||||
|
</AButton>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
infoBox: {
|
||||||
|
backgroundColor: '#f8f8f8',
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
fontSize: 16,
|
||||||
|
marginBottom: 8,
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 100,
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#888',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -59,8 +59,8 @@ export default function RootLayout() {
|
|||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerTitleAlign: 'center',
|
headerTitleAlign: 'center',
|
||||||
animation: 'slide_from_right',
|
animation: 'slide_from_right',
|
||||||
gestureEnabled: true, // 启用手势返回
|
gestureEnabled: true,
|
||||||
gestureDirection: 'horizontal', // 水平手势
|
gestureDirection: 'horizontal',
|
||||||
contentStyle: {
|
contentStyle: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
},
|
},
|
||||||
@@ -68,14 +68,18 @@ export default function RootLayout() {
|
|||||||
>
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="(tabs)"
|
name="(tabs)"
|
||||||
options={{ headerShown: false }}
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="auth"
|
name="auth"
|
||||||
options={{
|
options={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
|
|||||||
@@ -25,6 +25,5 @@ export default function AuthLayout() {
|
|||||||
<StepLayout steps={steps}>
|
<StepLayout steps={steps}>
|
||||||
<Slot />
|
<Slot />
|
||||||
</StepLayout>
|
</StepLayout>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,11 @@ import { IconQq, IconWeixin, IconZhiwen } from '@/components/iconfont'
|
|||||||
import { Divider } from '@/components/ui/divider'
|
import { Divider } from '@/components/ui/divider'
|
||||||
import useFormValidation from '@/hooks/useFormValidation'
|
import useFormValidation from '@/hooks/useFormValidation'
|
||||||
import { loginDefaultApi } from '@/request/api/auth'
|
import { loginDefaultApi } from '@/request/api/auth'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
interface LoginForm {
|
interface LoginForm {
|
||||||
username: string
|
account: string
|
||||||
password: string
|
password: string
|
||||||
rememberMe: boolean
|
rememberMe: boolean
|
||||||
socialType?: 'qq' | 'weixin' | 'fingerprint'
|
socialType?: 'qq' | 'weixin' | 'fingerprint'
|
||||||
@@ -17,10 +18,11 @@ interface LoginForm {
|
|||||||
|
|
||||||
export default function AuthLogin() {
|
export default function AuthLogin() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [form, setForm] = useState<LoginForm>({
|
const [form, setForm] = useState<LoginForm>({
|
||||||
username: '',
|
account: userStore.userInfo?.account || '',
|
||||||
password: '',
|
password: '',
|
||||||
rememberMe: true,
|
rememberMe: true,
|
||||||
})
|
})
|
||||||
@@ -30,7 +32,7 @@ export default function AuthLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
username: { required: true, message: '账号是必填项' },
|
account: { required: true, message: '账号是必填项' },
|
||||||
password: { required: true, message: '密码是必填项' },
|
password: { required: true, message: '密码是必填项' },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,23 +40,38 @@ export default function AuthLogin() {
|
|||||||
|
|
||||||
const loginDefault = () => {
|
const loginDefault = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
// loginDefaultApi({
|
loginDefaultApi({
|
||||||
// account: form.username,
|
account: form.account,
|
||||||
// password: form.password,
|
password: form.password,
|
||||||
// }).then((res) => {
|
}).then((res) => {
|
||||||
// if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
// router.replace('/')
|
toast.show({
|
||||||
// }
|
title: '登录成功',
|
||||||
// else {
|
})
|
||||||
// toast.show({
|
|
||||||
// title: res.message || '登录失败',
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }).catch((_err) => {
|
|
||||||
|
|
||||||
// }).finally(() => {
|
userStore.setUserInfo({
|
||||||
// setLoading(false)
|
account: form.account,
|
||||||
// })
|
id: res.data.userId,
|
||||||
|
seqNo: res.data.seqNo,
|
||||||
|
username: res.data.username,
|
||||||
|
gender: res.data.gender,
|
||||||
|
token: res.data.token,
|
||||||
|
refreshToken: res.data.refreshToken,
|
||||||
|
rememberMe: form.rememberMe,
|
||||||
|
})
|
||||||
|
|
||||||
|
router.replace('/user')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.show({
|
||||||
|
title: res.message || '登录失败',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch((_err) => {
|
||||||
|
|
||||||
|
}).finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginQq = () => {
|
const loginQq = () => {
|
||||||
@@ -78,8 +95,8 @@ export default function AuthLogin() {
|
|||||||
<AForm form={form}>
|
<AForm form={form}>
|
||||||
<AFormItem label="账号">
|
<AFormItem label="账号">
|
||||||
<AInput
|
<AInput
|
||||||
value={form.username}
|
value={form.account}
|
||||||
onChange={text => handleInputChange('username', text)}
|
onChange={text => handleInputChange('account', text)}
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ComponentType } from 'react'
|
import type { ComponentType } from 'react'
|
||||||
import type { StyleProp, ViewStyle } from 'react-native'
|
import type { StyleProp, ViewStyle } from 'react-native'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { Pressable } from 'react-native'
|
||||||
import { EyeIcon, EyeOffIcon } from '@/components/ui/icon'
|
import { EyeIcon, EyeOffIcon } from '@/components/ui/icon'
|
||||||
import { InputField, InputIcon, InputSlot, Input as NativeInput } from '@/components/ui/input'
|
import { InputField, InputIcon, InputSlot, Input as NativeInput } from '@/components/ui/input'
|
||||||
|
|
||||||
@@ -52,23 +53,23 @@ export default function Input({
|
|||||||
className={className}
|
className={className}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{leftIcon
|
{leftIcon && (
|
||||||
? (
|
<InputSlot className="pl-3">
|
||||||
<InputSlot className="pl-3">
|
<InputIcon as={leftIcon} />
|
||||||
<InputIcon as={leftIcon} />
|
</InputSlot>
|
||||||
</InputSlot>
|
)}
|
||||||
)
|
|
||||||
: null}
|
|
||||||
<InputField
|
<InputField
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={onChange}
|
onChangeText={onChange}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type={isPassword ? 'password' : 'text'}
|
type={isPassword ? (showPassword ? 'text' : 'password') : 'text'}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{isPassword && (
|
{isPassword && (
|
||||||
<InputSlot className="pr-3" onPress={() => setShowPassword(prev => !prev)}>
|
<InputSlot className="pr-3">
|
||||||
<InputIcon as={showPassword ? EyeIcon : EyeOffIcon} />
|
<Pressable onPress={() => setShowPassword(prev => !prev)}>
|
||||||
|
<InputIcon as={showPassword ? EyeIcon : EyeOffIcon} />
|
||||||
|
</Pressable>
|
||||||
</InputSlot>
|
</InputSlot>
|
||||||
)}
|
)}
|
||||||
</NativeInput>
|
</NativeInput>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Ionicons } from '@expo/vector-icons'
|
import { Ionicons } from '@expo/vector-icons'
|
||||||
import { usePathname, useRouter } from 'expo-router'
|
import { usePathname, useRouter } from 'expo-router'
|
||||||
|
import { useRef } from 'react'
|
||||||
import { Image, Pressable, Text, View } from 'react-native'
|
import { Image, Pressable, Text, View } from 'react-native'
|
||||||
import Animated, { SlideInLeft, SlideInRight } from 'react-native-reanimated'
|
import Animated, { SlideInLeft, SlideInRight } from 'react-native-reanimated'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
@@ -31,7 +32,15 @@ export default function StepLayout({ steps, children, showHearder = true }: Step
|
|||||||
// 是否显示header
|
// 是否显示header
|
||||||
const headerVisible = showHearder !== false && currentStep.showHearder !== false
|
const headerVisible = showHearder !== false && currentStep.showHearder !== false
|
||||||
|
|
||||||
const enteringAnimation = canGoBack ? SlideInRight.duration(300) : SlideInLeft.duration(300)
|
const hasMounted = useRef(false)
|
||||||
|
|
||||||
|
const enteringAnimation = hasMounted.current
|
||||||
|
? canGoBack
|
||||||
|
? SlideInRight.duration(300)
|
||||||
|
: SlideInLeft.duration(300)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
hasMounted.current = true
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { usePathname, useRouter } from 'expo-router'
|
||||||
|
import { TabList, Tabs, TabSlot, TabTrigger } from 'expo-router/ui'
|
||||||
|
import { icons } from 'lucide-react-native'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Pressable, View } from 'react-native'
|
||||||
|
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||||
|
import Animated, { useAnimatedStyle, useSharedValue, withSequence, withTiming } from 'react-native-reanimated'
|
||||||
|
import { scheduleOnRN } from 'react-native-worklets'
|
||||||
|
|
||||||
|
interface AppTabbarProps {
|
||||||
|
data: TabbarItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabbarItem {
|
||||||
|
icon: keyof typeof icons
|
||||||
|
iconSize?: number
|
||||||
|
activeIcon?: keyof typeof icons
|
||||||
|
name: string
|
||||||
|
href?: string
|
||||||
|
onPress?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabButton({
|
||||||
|
icon,
|
||||||
|
activeIcon,
|
||||||
|
iconSize = 24,
|
||||||
|
isFocused = false,
|
||||||
|
onPress,
|
||||||
|
}: {
|
||||||
|
icon: keyof typeof icons
|
||||||
|
activeIcon?: keyof typeof icons
|
||||||
|
iconSize?: number
|
||||||
|
isFocused?: boolean
|
||||||
|
onPress?: () => void
|
||||||
|
}) {
|
||||||
|
const scale = useSharedValue(1)
|
||||||
|
|
||||||
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ scale: scale.value }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isFocused) {
|
||||||
|
scale.value = withSequence(
|
||||||
|
withTiming(0.9, { duration: 120 }),
|
||||||
|
withTiming(1, { duration: 160 }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [isFocused])
|
||||||
|
|
||||||
|
const IconComponent = icons[isFocused && activeIcon ? activeIcon : icon]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={onPress}
|
||||||
|
style={{ flex: 1, alignItems: 'center', justifyContent: 'center', height: '100%' }}
|
||||||
|
>
|
||||||
|
<Animated.View style={animatedStyle}>
|
||||||
|
<IconComponent width={iconSize} height={iconSize} stroke={isFocused ? '#333' : '#aaa'} />
|
||||||
|
</Animated.View>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomTabbar({ data }: AppTabbarProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState<string>(() => {
|
||||||
|
const match = data.find(item => item.href === pathname)
|
||||||
|
return match?.name ?? data[0]?.name
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`pathname:${pathname}`)
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
// 反转方向
|
||||||
|
const [swipeDirection, setSwipeDirection] = useState<'left' | 'right'>('left')
|
||||||
|
function updateSwipeDirection(tabName: string) {
|
||||||
|
const currentIndex = data.findIndex(d => d.name === activeTab)
|
||||||
|
const targetIndex = data.findIndex(d => d.name === tabName)
|
||||||
|
|
||||||
|
if (currentIndex === -1 || targetIndex === -1 || currentIndex === targetIndex)
|
||||||
|
return
|
||||||
|
|
||||||
|
setSwipeDirection(targetIndex > currentIndex ? 'left' : 'right')
|
||||||
|
setActiveTab(tabName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击调用
|
||||||
|
const handlPressTab = (item: TabbarItem) => {
|
||||||
|
updateSwipeDirection(item.name)
|
||||||
|
|
||||||
|
setActiveTab(item.name)
|
||||||
|
if (typeof item.onPress === 'function')
|
||||||
|
item.onPress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义 JS 线程安全函数
|
||||||
|
const handlePanTab = (tabName: string) => {
|
||||||
|
const tab = data.find(item => item.name === tabName)
|
||||||
|
if (!tab)
|
||||||
|
return
|
||||||
|
|
||||||
|
updateSwipeDirection(tabName)
|
||||||
|
|
||||||
|
setActiveTab(tab.name)
|
||||||
|
|
||||||
|
if (typeof tab.onPress === 'function') {
|
||||||
|
tab.onPress()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.href && tab.href !== pathname) {
|
||||||
|
router.push(tab.href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手势滑动
|
||||||
|
const panGesture = Gesture.Pan()
|
||||||
|
.activeOffsetX([-20, 20])
|
||||||
|
.failOffsetY([-10, 10])
|
||||||
|
.onEnd((event) => {
|
||||||
|
const threshold = 60
|
||||||
|
const currentIndex = data.findIndex(item => item.name === activeTab)
|
||||||
|
let targetIndex: number | undefined
|
||||||
|
|
||||||
|
if (event.translationX < -threshold && currentIndex < data.length - 1) {
|
||||||
|
targetIndex = currentIndex + 1
|
||||||
|
}
|
||||||
|
else if (event.translationX > threshold && currentIndex > 0) {
|
||||||
|
targetIndex = currentIndex - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetIndex !== undefined) {
|
||||||
|
const tabName = data[targetIndex].name
|
||||||
|
scheduleOnRN(handlePanTab, tabName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
|
<Tabs>
|
||||||
|
<GestureDetector gesture={panGesture}>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
{(() => {
|
||||||
|
const currentTab = data.find(d => d.name === activeTab)
|
||||||
|
if (!currentTab)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
key={activeTab}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TabSlot />
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</View>
|
||||||
|
</GestureDetector>
|
||||||
|
|
||||||
|
<TabList asChild>
|
||||||
|
<View style={{ flexDirection: 'row', backgroundColor: 'white', height: 48, alignItems: 'center' }}>
|
||||||
|
{data.map((item) => {
|
||||||
|
if (item.href) {
|
||||||
|
return (
|
||||||
|
<TabTrigger key={item.name} name={item.name} href={item.href} asChild>
|
||||||
|
<TabButton
|
||||||
|
icon={item.icon}
|
||||||
|
activeIcon={item.activeIcon}
|
||||||
|
iconSize={item.iconSize}
|
||||||
|
isFocused={activeTab === item.name}
|
||||||
|
onPress={() => handlPressTab(item)}
|
||||||
|
/>
|
||||||
|
</TabTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabButton
|
||||||
|
key={item.name}
|
||||||
|
icon={item.icon}
|
||||||
|
activeIcon={item.activeIcon}
|
||||||
|
iconSize={item.iconSize}
|
||||||
|
isFocused={activeTab === item.name}
|
||||||
|
onPress={() => handlPressTab(item)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</TabList>
|
||||||
|
</Tabs>
|
||||||
|
</GestureHandlerRootView>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { usePathname, useRouter } from 'expo-router'
|
import { Redirect, usePathname, useRouter, useSegments } from 'expo-router'
|
||||||
import { TabList, Tabs, TabSlot, TabTrigger } from 'expo-router/ui'
|
import { TabList, Tabs, TabSlot, TabTrigger } from 'expo-router/ui'
|
||||||
import { icons } from 'lucide-react-native'
|
import { icons } from 'lucide-react-native'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||||
import Animated, { SlideInLeft, SlideInRight, useAnimatedStyle, useSharedValue, withSequence, withTiming } from 'react-native-reanimated'
|
import Animated, { useAnimatedStyle, useSharedValue, withSequence, withTiming } from 'react-native-reanimated'
|
||||||
import { scheduleOnRN } from 'react-native-worklets'
|
import { scheduleOnRN } from 'react-native-worklets'
|
||||||
|
|
||||||
interface AppTabbarProps {
|
interface AppTabbarProps {
|
||||||
@@ -141,29 +141,25 @@ export default function CustomTabbar({ data }: AppTabbarProps) {
|
|||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
{(() => {
|
{(() => {
|
||||||
const currentTab = data.find(d => d.name === activeTab)
|
const currentTab = data.find(d => d.name === activeTab)
|
||||||
|
|
||||||
if (!currentTab)
|
if (!currentTab)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return currentTab.href
|
return (
|
||||||
? (
|
<Animated.View
|
||||||
<Animated.View
|
style={{
|
||||||
key={activeTab}
|
flex: 1,
|
||||||
entering={swipeDirection === 'left' ? SlideInRight.duration(300) : SlideInLeft.duration(300)}
|
backgroundColor: 'transparent',
|
||||||
style={{ flex: 1 }}
|
}}
|
||||||
>
|
>
|
||||||
<TabSlot />
|
<TabSlot />
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
: (
|
|
||||||
<TabSlot />
|
|
||||||
)
|
|
||||||
})()}
|
})()}
|
||||||
</View>
|
</View>
|
||||||
</GestureDetector>
|
</GestureDetector>
|
||||||
|
|
||||||
<TabList asChild>
|
<TabList asChild>
|
||||||
<View style={{ flexDirection: 'row', backgroundColor: 'white', height: 48, alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', height: 48, alignItems: 'center' }}>
|
||||||
{data.map((item) => {
|
{data.map((item) => {
|
||||||
if (item.href) {
|
if (item.href) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,7 +15,14 @@ interface LoginDefaultParams {
|
|||||||
account: string
|
account: string
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
interface LoginDefaultResult {
|
||||||
|
token: string
|
||||||
|
refreshToken: string
|
||||||
|
userId: string
|
||||||
|
username: string
|
||||||
|
seqNo: number
|
||||||
|
gender: number
|
||||||
|
}
|
||||||
export function loginDefaultApi(params: LoginDefaultParams) {
|
export function loginDefaultApi(params: LoginDefaultParams) {
|
||||||
return alovaInstance.Post<ResponseData<any>>(`${API_PREFIX}/login/default`, params)
|
return alovaInstance.Post<ResponseData<LoginDefaultResult>>(`${API_PREFIX}/login/default`, params)
|
||||||
}
|
}
|
||||||
@@ -17,5 +17,6 @@ declare global {
|
|||||||
gender: number
|
gender: number
|
||||||
token?: string
|
token?: string
|
||||||
refreshToken?: string
|
refreshToken?: string
|
||||||
|
rememberMe?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"base-go-v2/internal/db"
|
"base-go-v2/internal/db"
|
||||||
response "base-go-v2/internal/reponse"
|
response "base-go-v2/internal/reponse"
|
||||||
"base-go-v2/internal/router"
|
"base-go-v2/internal/router"
|
||||||
|
"base-go-v2/internal/routinex"
|
||||||
"base-go-v2/internal/utils/jwtx"
|
"base-go-v2/internal/utils/jwtx"
|
||||||
"base-go-v2/internal/utils/mapx"
|
"base-go-v2/internal/utils/mapx"
|
||||||
|
"base-go-v2/internal/utils/timex"
|
||||||
"base-go-v2/internal/utils/uid"
|
"base-go-v2/internal/utils/uid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,10 +104,17 @@ func loginDefault(c *router.Context) error {
|
|||||||
"username", user.GetString("username"),
|
"username", user.GetString("username"),
|
||||||
"seqNo", user.GetInt("seq_no"),
|
"seqNo", user.GetInt("seq_no"),
|
||||||
"gender", user.GetInt("gender"),
|
"gender", user.GetInt("gender"),
|
||||||
"accessToken", accessToken,
|
"token", accessToken,
|
||||||
"refreshToken", refreshToken,
|
"refreshToken", refreshToken,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
routinex.Go(func() {
|
||||||
|
kv := mapx.New().SetKV(
|
||||||
|
"last_login_time", timex.NowString(),
|
||||||
|
)
|
||||||
|
_, _ = db.UpdateOne("user_info", "id", userId, kv)
|
||||||
|
})
|
||||||
|
|
||||||
return response.Success(c).
|
return response.Success(c).
|
||||||
Data(respData).
|
Data(respData).
|
||||||
Send()
|
Send()
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package timex
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// NowTime 返回当前时间对象(本地时区)
|
||||||
|
func NowTime() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NowString 返回当前时间的字符串格式:YYYY-MM-DD HH:mm:ss
|
||||||
|
func NowString() string {
|
||||||
|
return time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateString 返回当前日期(年月日):YYYY-MM-DD
|
||||||
|
func DateString() string {
|
||||||
|
return time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeString 返回当前时间(时分秒):HH:mm:ss
|
||||||
|
func TimeString() string {
|
||||||
|
return time.Now().Format("15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomFormat 返回指定格式的当前时间
|
||||||
|
// 例如:timeutil.CustomFormat("2006年01月02日 15时04分05秒")
|
||||||
|
func CustomFormat(format string) string {
|
||||||
|
return time.Now().Format(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NowUnix 返回当前时间戳(秒)
|
||||||
|
func NowUnix() int64 {
|
||||||
|
return time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NowUnixMilli 返回当前时间戳(毫秒)
|
||||||
|
func NowUnixMilli() int64 {
|
||||||
|
return time.Now().UnixMilli()
|
||||||
|
}
|
||||||
Reference in new issue
Block a user