This commit is contained in:
tx 2024-12-18 16:21:19 +08:00
parent a868525bb4
commit 17c5a983e6
11 changed files with 831 additions and 425 deletions

4
package-lock.json generated
View File

@ -2302,8 +2302,7 @@
"node_modules/@eva-design/eva": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/@eva-design/eva/-/eva-2.2.0.tgz",
"integrity": "sha512-Wh98ex5cCK+YYSQNpthX1bT4CA3zDRR1WnJv0YlyvULAkmjaEvqtoGMCXzu5DH8v1fGIggu/OpAokLS7UVPe+A==",
"license": "MIT"
"integrity": "sha512-Wh98ex5cCK+YYSQNpthX1bT4CA3zDRR1WnJv0YlyvULAkmjaEvqtoGMCXzu5DH8v1fGIggu/OpAokLS7UVPe+A=="
},
"node_modules/@eva-design/processor": {
"version": "2.2.0",
@ -4226,7 +4225,6 @@
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/@ui-kitten/components/-/components-5.3.1.tgz",
"integrity": "sha512-Oj1WePUQtpNfH7ftXGdkkFVmJI+JcR3cBryPJV0E+JAUdH2dbJ0oG/VA+UAgk27/u0K0OZSUkdMFuGnkDAVuYA==",
"license": "MIT",
"dependencies": {
"@eva-design/dss": "^2.2.0",
"@eva-design/processor": "^2.2.0",

View File

@ -1,39 +1,69 @@
import React, { useRef, useEffect, useState } from 'react';
import { View, Text, StyleSheet, Animated, Image, PanResponder, Vibration } from 'react-native';
import { rpx } from '../utils/rpx';
import { View, Text, StyleSheet, Animated, Image, PanResponder, Vibration, Dimensions } from 'react-native';
const Slider: React.FC = () => {
const translateX = useRef(new Animated.Value(0)).current;
interface SliderProps {
lockStatus?: number;
onStatusChange?: (status: boolean) => void;
}
// 添加 rpx 计算函数
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const rpx = (px: number) => {
return (SCREEN_WIDTH / 750) * px;
};
const Slider: React.FC<SliderProps> = ({ lockStatus = 1, onStatusChange }) => {
const translateX = useRef(new Animated.Value(lockStatus === 0 ? rpx(180) : 0)).current;
const maxWidth = rpx(180);
const buttonWidth = rpx(86);
const [iconOpacity, setIconOpacity] = useState(1);
const [isRight, setIsRight] = useState(false);
const [isRight, setIsRight] = useState(lockStatus === 0);
const hasVibratedRef = useRef(false);
const offsetRef = useRef(0);
// 添加监听器
useEffect(() => {
const id = translateX.addListener(({ value }) => {
if (value >= maxWidth * 0.95) {
setIsRight(true);
} else if (value <= maxWidth * 0.05) {
setIsRight(false);
}
});
return () => translateX.removeListener(id);
}, []);
const iconOpacity = translateX.interpolate({
inputRange: [maxWidth * 0.9, maxWidth],
outputRange: [1, 0],
extrapolate: 'clamp'
});
const backgroundWidth = translateX.interpolate({
inputRange: [0, maxWidth],
outputRange: [buttonWidth - rpx(2), maxWidth + buttonWidth - rpx(2)],
extrapolate: 'clamp'
});
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (_, gestureState) => {
return Math.abs(gestureState.dx) > 10;
},
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
hasVibratedRef.current = false;
offsetRef.current = translateX._value;
},
onPanResponderMove: (_, gestureState) => {
const currentValue = translateX.__getValue();
let newValue;
if (isRight) {
newValue = maxWidth + gestureState.dx;
newValue = offsetRef.current + gestureState.dx;
} else {
newValue = gestureState.dx;
newValue = offsetRef.current + gestureState.dx;
}
newValue = Math.max(0, Math.min(maxWidth, newValue));
translateX.setValue(newValue);
// 根据滑动位置更新状态
setIsRight(newValue > maxWidth / 2);
if (!hasVibratedRef.current) {
if (newValue >= maxWidth * 0.95 || newValue <= maxWidth * 0.05) {
Vibration.vibrate(20);
@ -42,98 +72,86 @@ const Slider: React.FC = () => {
}
},
onPanResponderRelease: (_, gestureState) => {
const currentValue = translateX.__getValue();
const currentValue = translateX._value;
let toValue;
let finalIsRight;
if (isRight) {
if (gestureState.dx < -maxWidth / 4) {
toValue = 0;
setIsRight(false);
finalIsRight = false;
} else {
toValue = maxWidth;
setIsRight(true);
finalIsRight = true;
}
} else {
if (gestureState.dx > maxWidth / 4) {
toValue = maxWidth;
setIsRight(true);
finalIsRight = true;
} else {
toValue = 0;
setIsRight(false);
finalIsRight = false;
}
}
Animated.spring(translateX, {
Animated.timing(translateX, {
toValue,
tension: 50,
friction: 7,
duration: 100,
useNativeDriver: false,
}).start(() => {
hasVibratedRef.current = false;
onStatusChange?.(finalIsRight);
});
},
})
).current;
const backgroundColorWidth = translateX.interpolate({
inputRange: [0, maxWidth],
outputRange: [0, maxWidth + buttonWidth],
extrapolate: 'clamp',
});
useEffect(() => {
const listenerId = translateX.addListener(({ value }) => {
const newOpacity = value < maxWidth ? (1 - value / maxWidth) : 0;
setIconOpacity(newOpacity);
});
return () => {
translateX.removeListener(listenerId);
};
}, [translateX]);
return (
<View style={styles.container}>
<View style={styles.car_Opne_box}>
{/* 默认背景 */}
<View style={styles.defaultBackground} />
{/* 蓝色滑动背景 */}
<Animated.View
style={[
styles.background,
{
width: backgroundColorWidth,
backgroundColor: '#4297F3',
zIndex: 1,
width: backgroundWidth,
}
]}
/>
<View style={[styles.defaultBackground]} />
{/* 滑块 */}
<Animated.View
{...panResponder.panHandlers}
style={[
styles.imageContainer,
{
transform: [{
translateX
}],
zIndex: 2,
transform: [{ translateX }]
}
]}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uY9tYXXZztuE1VTLDl5y' }}
style={styles.imageContent}
resizeMode="contain"
/>
</Animated.View>
{/* 箭头图标 */}
<Animated.Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uEJob4XbADaL9ohOTVTL' }}
style={[
styles.icon,
{ opacity: iconOpacity, zIndex: 2 }
{ opacity: iconOpacity }
]}
resizeMode="contain"
/>
</View>
<Text style={styles.text}>{isRight ? '左滑关闭' : '右滑启动'}</Text>
<Text style={styles.text}>
{isRight ? '左滑关闭' : '右滑启动'}
</Text>
</View>
);
};
@ -153,17 +171,23 @@ const styles = StyleSheet.create({
overflow: 'hidden',
},
background: {
height: '100%',
borderRadius: rpx(45),
position: 'absolute',
left: 0,
top: 0,
height: '100%',
backgroundColor: '#4297F3',
borderRadius: rpx(45),
zIndex: 1,
},
defaultBackground: {
width: rpx(268),
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
backgroundColor: '#EBEBEB',
borderRadius: rpx(45),
zIndex: 0,
},
imageContainer: {
width: rpx(86),
@ -195,4 +219,4 @@ const styles = StyleSheet.create({
},
});
export default Slider;
export default React.memo(Slider);

View File

@ -14,7 +14,7 @@ import AddShare from '../views/device/AddShare';
import ShareQrcode from '../views/device/shareQrcode';
import { getFocusedRouteNameFromRoute } from '@react-navigation/native';
import { HomeStackParamList } from './types';
import TestBule from '../views/device/test_bule';
const Stack = createStackNavigator<HomeStackParamList>();
const createScreenOptions = (title: string): StackNavigationOptions => {
@ -50,10 +50,19 @@ export default function HomeStackNavigator({ navigation, route }: Props) {
headerShown: false,
}}
/>
<Stack.Screen
name="TestBule"
component={TestBule}
options={{
headerShown: false,
}}
/>
<Stack.Screen
name="DeviceList"
component={DeviceList}
options={createScreenOptions('设备列表')}
options={{
headerShown: false,
}}
/>
<Stack.Screen
name="BindIndex"

View File

@ -7,7 +7,6 @@ import { BottomNavigation, BottomNavigationTab, Icon } from '@ui-kitten/componen
import { getFocusedRouteNameFromRoute } from '@react-navigation/native';
import { useAuth } from '../context/AuthContext';
// 导入页面组件
import LoginScreen from '../views/Login/Login';
import ResetPasswordScreen from '../views/Login/ResetPassword';
@ -19,8 +18,8 @@ import { RootStackParamList, MainTabParamList } from './types';
const Stack = createStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<MainTabParamList>();
// 定义需要显示绑定底部栏的路由名称
const bindNavBarRoutes = ['BindIndex', 'SnBind', 'ConfirmBind'];
// 定义需要显示底部导航栏的路由名称
const showTabBarRoutes = ['爱车', '个人中心'];
const MainNavigator = () => {
return (
@ -30,8 +29,8 @@ const MainNavigator = () => {
tabBarHideOnKeyboard: true,
}}
tabBar={props => {
const routeName = getFocusedRouteNameFromRoute(props.state.routes[props.state.index]) ?? 'Home';
if (bindNavBarRoutes.includes(routeName)) {
const routeName = getFocusedRouteNameFromRoute(props.state.routes[props.state.index]) ?? '爱车';
if (!showTabBarRoutes.includes(routeName)) {
return null;
}
return (

View File

@ -27,6 +27,7 @@ export type HomeStackParamList = {
DeviceShare: undefined;
AddShare: undefined;
ShareQrcode: undefined;
TestBule: undefined;
};
// 导航属性类型

View File

@ -26,7 +26,7 @@ const api = axios.create({
api.interceptors.request.use(
async (config) => {
if (__DEV__) {
console.log('[Request]:', config.url, config.data);
// console.log('[Request]:', config.url, config.data);
}
const token = await auth.getToken();
@ -42,7 +42,7 @@ api.interceptors.request.use(
api.interceptors.response.use(
response => {
if (__DEV__) {
console.log('[Response]:', response.config.url, response.data);
// console.log('[Response]:', response.config.url, response.data);
}
const { code, msg, data } = response.data;
@ -100,5 +100,7 @@ export const apiService = {
bindSn: (sn: string) =>
api.post('/appVerify/userBandDevice?sn=' + sn),
getDeviceList: () => api.get('/appVerify/getDeviceListByToken'),
getDeviceList: () => api.get('/appVerify/getDeviceListByMerchantToken'),
toggleDefault: (sn: string) => api.put(`/appVerify/toggleDefault?sn=${sn}`),
};

View File

@ -37,7 +37,8 @@ const HomeScreen = () => {
const fetchDeviceList = async () => {
try {
const response = await apiService.getDeviceList();
if (response && response.data && response.data.length > 1) {
console.log(response.data.length,'response');
if (response && response.data && response.data.length > 0) {
setHasMultipleDevices(true);
} else {
setHasMultipleDevices(false);

View File

@ -1,14 +1,14 @@
import React, { useEffect, useState, useRef } from 'react';
import {
View,
Text,
StyleSheet,
Image,
PanResponder,
Animated,
ScrollView,
import {
View,
Text,
StyleSheet,
Image,
PanResponder,
Animated,
ScrollView,
TouchableOpacity,
TouchableWithoutFeedback ,
TouchableWithoutFeedback,
StatusBar
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
@ -17,15 +17,26 @@ import http from '../../utils/http';
import { rpx } from '../../utils/rpx';
import Slider from '../../components/slider';
import MiniMap from './MiniMap';
import { apiService } from '../../utils/api';
// 定义导航参数类型
type RootStackParamList = {
Home: undefined;
DeviceList: undefined;
DeviceMap: undefined;
DeviceSet: undefined;
DeviceShare: undefined;
// 添加其他页面的路由参数类型
Home: undefined;
DeviceList: undefined;
DeviceMap: undefined;
DeviceSet: undefined;
DeviceShare: undefined;
TestBule: undefined;
// 添加其他页面的路由参数类型
};
type DeviceType = {
id: number;
sn: string;
vehicleNum: string;
model: string;
remainingPower: number;
remainingMileage: number;
isDefault: number;
lockStatus: number;
};
// 定义导航类型
@ -37,7 +48,7 @@ const NormaIndex: React.FC = () => {
const translateX = useRef(new Animated.Value(0)).current;
const bgColor = useRef(new Animated.Value(0)).current;
const navigation = useNavigation<NavigationProp>();
const [defaultDevice, setDefaultDevice] = useState<DeviceType | null>(null);
const handlePress = () => {
navigation.navigate('DeviceList');
};
@ -51,7 +62,9 @@ const NormaIndex: React.FC = () => {
const toShare = () => {
navigation.navigate('DeviceShare');
};
const toTestBule = () => {
navigation.navigate('TestBule');
};
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
@ -102,154 +115,185 @@ const NormaIndex: React.FC = () => {
});
useEffect(() => {
http.get('/app/article/9')
.then(response => {
// setData(response);
})
.catch(error => {
console.error('请求错误', error);
});
fetchDeviceList();
}, []);
const getPowerColor = (power: number): string => {
if (power >= 60) {
return 'rgba(89, 202, 112, 0.5)'; // 绿色
} else if (power >= 20) {
return 'rgba(255, 149, 0, 0.5)'; // 橙色
} else {
return 'rgba(255, 69, 58, 0.5)'; // 红色
}
};
const fetchDeviceList = async () => {
try {
const response = await apiService.getDeviceList();
if (response?.code === 200 && response.data) {
const defaultDev = response.data.find((device: DeviceType) => device.isDefault == 1);
if (defaultDev) {
console.log(defaultDev, 'defaultDev');
setDefaultDevice(defaultDev);
}
}
} catch (error) {
console.error('获取设备列表失败:', error);
} finally {
}
};
return (
<View style={styles.pageContainer}>
<StatusBar
backgroundColor="#F3FCFF" // 设置为白色
barStyle="dark-content" // 状态栏文字为深色
translucent={false} // 不透明
/>
<ScrollView
contentContainerStyle={styles.scrollContent}
scrollEventThrottle={16}
nestedScrollEnabled={true}
>
<View style={styles.container}>
<TouchableOpacity onPress={handlePress}>
<View style={styles.titBox}>
<Text
style={styles.titTxts}
numberOfLines={1}
ellipsizeMode="tail"
>
VFLU-13762
</Text>
<Image
source={{ uri: 'https://api.ccttiot.com/smartmeter/img/static/uJRIitv0Yn5K7CEVe9qd' }}
style={{ width: rpx(24), height: rpx(14), marginLeft: rpx(8) }}
/>
</View>
</TouchableOpacity>
<View style={styles.KmBox}>
<View style={styles.KmLi}>
<View style={styles.KmLi_top}>
<Text style={styles.titTxt}>110</Text>
<Text style={styles.KmLi_tits}>km</Text>
</View>
<View style={styles.KmLi_bot}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uDg93bCzlPFiFBtS71Al' }}
style={{ width: rpx(32), height: rpx(32) }}
<StatusBar
backgroundColor="#F3FCFF" // 设置为白色
barStyle="dark-content" // 状态栏文字为深色
translucent={false} // 不透明
/>
<ScrollView
contentContainerStyle={styles.scrollContent}
scrollEventThrottle={16}
nestedScrollEnabled={true}
>
<View style={styles.container}>
<TouchableOpacity onPress={handlePress}>
<View style={styles.titBox}>
<Text
style={styles.titTxts}
numberOfLines={2}
ellipsizeMode="tail"
>
{defaultDevice?.model || '未选择车辆'}
</Text>
<Image
source={{ uri: 'https://api.ccttiot.com/smartmeter/img/static/uJRIitv0Yn5K7CEVe9qd' }}
style={styles.arrowIcon}
/>
<Text style={styles.KmLi_tits1}></Text>
</View>
</View>
<View style={styles.KmLi}>
<View style={styles.KmLi_top}>
<Text style={styles.titTxt}>110</Text>
<Text style={styles.KmLi_tits}>km</Text>
</View>
<View style={styles.KmLi_bot}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uDg93bCzlPFiFBtS71Al' }}
style={{ width: rpx(32), height: rpx(32) }}
/>
<Text style={styles.KmLi_tits1}></Text>
</View>
</View>
</View>
<View style={styles.infoBox}>
<View style={styles.eleBox}>
<View style={styles.eleType}>
<Text style={styles.eleTypeTxt}>90%</Text>
</View>
</View>
<View style={styles.carBox}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uVnIDwcwQP7oo12PeYVJ' }}
style={{ width: rpx(440), height: rpx(340) }}
/>
<View style={styles.txtbox}>
<View style={styles.yuan}></View>
<Text style={styles.txt}></Text>
</View>
</View>
<View style={styles.Bind_type}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uQdjlI2DLfXmABfynycn' }}
style={{ width: rpx(32), height: rpx(32) }}
/>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uLizPj6UxdjBsiqhxZB8' }}
style={{ width: rpx(60), height: rpx(60), marginLeft: 'auto' }}
/>
</View>
</View>
<View style={styles.car_stause_box}>
<View style={styles.car_stause_li}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uro1vIU1WydjNWgi7PUg' }}
style={{ width: rpx(90), height: rpx(90), marginLeft: rpx(18) }}
/>
<Text style={styles.stauseText}></Text>
</View>
<Slider
onComplete={() => {
console.log('滑动完成');
// 处理滑动完成后的逻辑
}}
/>
<View style={styles.car_stause_li}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uVpJNxwWXlyXt4IdHQoe' }}
style={{ width: rpx(90), height: rpx(90), marginLeft: rpx(18) }}
/>
<Text style={styles.stauseText}></Text>
</View>
</View>
<TouchableWithoutFeedback >
<View style={styles.mapWrapper}>
<MiniMap />
</View>
</TouchableWithoutFeedback>
<TouchableOpacity onPress={toSet}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/ucYQHQ2Ep4odL8JpbtfT' }}
style={styles.carSet}
/>
</TouchableOpacity>
<View style={styles.otherSet}>
<TouchableOpacity onPress={toShare}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/ulDHhC4MrH3FO0AeTqVg' }}
style={styles.otherImg}
/>
</TouchableOpacity>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u849NsNxdtzxhUkUJnfW' }}
style={styles.otherImg}
/>
<View style={styles.KmBox}>
<View style={styles.KmLi}>
<View style={styles.KmLi_top}>
<Text style={styles.titTxt}>{defaultDevice?.remainingMileage}</Text>
<Text style={styles.KmLi_tits}>km</Text>
</View>
<View style={styles.KmLi_bot}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uDg93bCzlPFiFBtS71Al' }}
style={{ width: rpx(32), height: rpx(32) }}
/>
<Text style={styles.KmLi_tits1}></Text>
</View>
</View>
<View style={styles.KmLi}>
<View style={styles.KmLi_top}>
<Text style={styles.titTxt}>110</Text>
<Text style={styles.KmLi_tits}>km</Text>
</View>
<View style={styles.KmLi_bot}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uDg93bCzlPFiFBtS71Al' }}
style={{ width: rpx(32), height: rpx(32) }}
/>
<Text style={styles.KmLi_tits1}></Text>
</View>
</View>
</View>
<View style={styles.infoBox}>
<View style={styles.eleBox}>
<View style={[
styles.eleType,
{
height: `${defaultDevice?.remainingPower || 0}%`,
backgroundColor: getPowerColor(defaultDevice?.remainingPower || 0)
}
]}>
<Text style={styles.eleTypeTxt}>{defaultDevice?.remainingPower || 0}%</Text>
</View>
</View>
<View style={styles.carBox}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uVnIDwcwQP7oo12PeYVJ' }}
style={{ width: rpx(440), height: rpx(340) }}
/>
<View style={styles.txtbox}>
<View style={styles.yuan}></View>
<Text style={styles.txt}> {defaultDevice?.lockStatus == 1 ? '开锁' : '关锁'}</Text>
</View>
</View>
<TouchableOpacity onPress={toTestBule}>
<View style={styles.Bind_type}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uQdjlI2DLfXmABfynycn' }}
style={{ width: rpx(32), height: rpx(32) }}
/>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uLizPj6UxdjBsiqhxZB8' }}
style={{ width: rpx(60), height: rpx(60), marginLeft: 'auto' }}
/>
</View>
</TouchableOpacity>
</View>
<View style={styles.car_stause_box}>
<View style={styles.car_stause_li}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uro1vIU1WydjNWgi7PUg' }}
style={{ width: rpx(90), height: rpx(90), marginLeft: rpx(18) }}
/>
<Text style={styles.stauseText}></Text>
</View>
<Slider
lockStatus={defaultDevice?.lockStatus}
onStatusChange={(status) => {
// 处理状态改变
console.log('Lock status changed:', status);
}}
/>
<View style={styles.car_stause_li}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uVpJNxwWXlyXt4IdHQoe' }}
style={{ width: rpx(90), height: rpx(90), marginLeft: rpx(18) }}
/>
<Text style={styles.stauseText}></Text>
</View>
</View>
<TouchableWithoutFeedback >
<View style={styles.mapWrapper}>
<MiniMap />
</View>
</TouchableWithoutFeedback>
<TouchableOpacity onPress={toSet}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/ucYQHQ2Ep4odL8JpbtfT' }}
style={styles.carSet}
/>
</TouchableOpacity>
<View style={styles.otherSet}>
<TouchableOpacity onPress={toShare}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/ulDHhC4MrH3FO0AeTqVg' }}
style={styles.otherImg}
/>
</TouchableOpacity>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u849NsNxdtzxhUkUJnfW' }}
style={styles.otherImg}
/>
</View>
</View>
</View>
</ScrollView>
</ScrollView>
</View>
);
};
@ -266,12 +310,12 @@ const styles = StyleSheet.create({
marginVertical: rpx(20),
},
stauseText: {
fontSize: rpx(32),
color: '#3D3D3D',
textAlign: 'center',
fontSize: rpx(32),
color: '#3D3D3D',
textAlign: 'center',
marginTop: rpx(24)
},
otherSet: {
marginTop: rpx(30),
// display:f,\
@ -355,14 +399,6 @@ const styles = StyleSheet.create({
borderRadius: rpx(11),
backgroundColor: 'rgba(255, 130, 130, 1)',
},
eleTypeTxt: {
position: 'absolute',
top: rpx(16),
left: rpx(6),
fontSize: rpx(32),
color: '#3D3D3D',
fontWeight: 'bold'
},
eleBox: {
paddingTop: rpx(14),
paddingRight: rpx(6),
@ -371,29 +407,59 @@ const styles = StyleSheet.create({
width: rpx(86),
height: rpx(166),
borderRadius: rpx(16),
backgroundColor: '#fff'
backgroundColor: '#FFFFFF',
justifyContent: 'flex-end',
// 添加阴影效果
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 4, // Android 阴影
// 添加边框使其更加醒目
borderWidth: 1,
borderColor: '#E0E0E0',
},
eleType: {
width: '100%',
height: '100%',
minHeight: rpx(30),
borderRadius: rpx(16),
backgroundColor: 'rgba(89,202,112,0.5)'
position: 'relative',
},
eleTypeTxt: {
position: 'absolute',
top: rpx(-40),
width: '100%',
textAlign: 'center',
fontSize: rpx(32),
color: '#3D3D3D',
// fontWeight: 'bold',
// 为文字添加阴影效果使其更清晰
textShadowColor: 'rgba(255, 255, 255, 0.5)',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 2
},
titBox: {
flexDirection: 'row',
alignItems: 'center',
display: 'flex',
flexWrap: 'nowrap',
maxWidth: rpx(600), // 增加最大宽度
},
titTxts: {
maxWidth: rpx(480),
width: rpx(250),
fontSize: rpx(48),
fontWeight: '500',
color: '#3D3D3D',
// flex: 2, // 添加 flex 属性
},
titBox: {
// width: rpx(750),
flexDirection: 'row', // 添加这行
alignItems: 'center', // 添加这行
// // 移除这些不需要的属性
display: 'flex',
flexWrap: 'nowrap',
// // flex: 1,
// justifyContent: 'center',
arrowIcon: {
width: rpx(24),
height: rpx(14),
marginLeft: rpx(8)
},
KmLi_bot: {
marginTop: rpx(4),

View File

@ -1,63 +1,237 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { auth } from '../utils/auth';
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
ScrollView,
Image,
View,
TouchableOpacity,
ImageBackground
} from 'react-native';
import { Text } from '@ui-kitten/components';
import { useNavigation } from '@react-navigation/native';
import { useAuth } from '../context/AuthContext'; //
import axios from 'axios';
import { useAuth } from '../context/AuthContext';
import { rpx } from '../utils/rpx';
import { apiService } from '../utils/api';
const ProfileScreen = () => {
const navigation = useNavigation();
const { setIsLoggedIn } = useAuth(); //
const { setIsLoggedIn } = useAuth();
const [userInfo, setUserInfo] = useState({});
const handleLogout = async () => {
useEffect(() => {
getUserInfo();
}, []);
const formatName = (name) => {
if (!name) return '';
return name.charAt(0) + '*'.repeat(name.length - 1);
};
const getUserInfo = async () => {
try {
await auth.removeToken(); // token
setIsLoggedIn(false); //
// AppNavigator isLoggedIn
const response = await apiService.getUserInfo();
if (response.code === 200) {
setUserInfo(response.user);
}
} catch (error) {
console.error('退出登录失败:', error);
console.error('获取用户信息失败:', error);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>个人中心</Text>
<TouchableOpacity
style={styles.logoutButton}
onPress={handleLogout}
>
<Text style={styles.logoutText}>退出登录</Text>
</TouchableOpacity>
</View>
<ImageBackground
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uYRs7Cv2Pbp95w3KjGO3' }}
style={styles.background}
>
<ScrollView style={styles.scrollView}>
<View style={styles.page}>
{/* 头部区域 */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u3giTY4VkWYpnGWRuFHF' }}
style={styles.avatar}
/>
<View style={styles.userinfo}>
<Text style={styles.username}>
{formatName(userInfo.realName)}
</Text>
<Text style={styles.userphone}>
{userInfo.phonenumber}
</Text>
</View>
</View>
<TouchableOpacity
style={styles.headerRight}
onPress={() => navigation.navigate('QrBind')}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uyb0iFo50FJ0MZg3RKkV' }}
style={styles.code}
/>
</TouchableOpacity>
</View>
{/* <Text style={styles.tit}>我的设备</Text> */}
{/* 管理与服务区域 */}
<Text style={styles.tit}>管理与服务</Text>
<View style={styles.content}>
<TouchableOpacity
style={styles.item}
onPress={() => navigation.navigate('OrderList')}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uVEbrRDbZXvELwK73KAi' }}
style={styles.itemIcon}
/>
<Text style={styles.itemText}>我的订单</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.item}
onPress={() => navigation.navigate('HelpCenter')}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uO1ju1OpuA5jjMR7bLYh' }}
style={styles.itemIcon}
/>
<Text style={styles.itemText}>帮助和客服</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.item}
onPress={() => navigation.navigate('Feedback')}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uHNMNwTRw89NhtgZUNXK' }}
style={styles.itemIcon}
/>
<Text style={styles.itemText}>意见和反馈</Text>
</TouchableOpacity>
{userInfo.isAuthentication === false && (
<TouchableOpacity
style={styles.item}
onPress={() => navigation.navigate('IdVerification')}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/unrltNyYYRXUutaqtuJY' }}
style={styles.itemIcon}
/>
<Text style={styles.itemText}>实名认证</Text>
</TouchableOpacity>
)}
{userInfo.userType === '02' && (
<TouchableOpacity
style={[styles.item, styles.lastItem]}
onPress={() => navigation.navigate('MerchantPortal')}
>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u1SsqJYSQ8jTK9PkhFtF' }}
style={styles.itemIcon}
/>
<Text style={styles.itemText}>商户端</Text>
</TouchableOpacity>
)}
</View>
{/* TabBar */}
{/* <View style={styles.tabBarContainer}>
<TabBar indexs={1} />
</View> */}
</View>
</ScrollView>
</ImageBackground>
);
};
const styles = StyleSheet.create({
container: {
background: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F3FCFF',
width: '100%',
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginTop: 20,
scrollView: {
flex: 1,
},
logoutButton: {
position: 'absolute',
bottom: 40,
width: '90%',
height: 44,
backgroundColor: '#FF4D4F',
borderRadius: 22,
justifyContent: 'center',
page: {
flex: 1,
paddingTop: rpx(200),
paddingHorizontal: rpx(32),
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
logoutText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '500',
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
},
avatar: {
width: rpx(108),
height: rpx(108),
borderRadius: rpx(54),
},
userinfo: {
marginLeft: rpx(40),
},
username: {
fontWeight: '700',
fontSize: rpx(36),
color: '#3D3D3D',
},
userphone: {
fontWeight: '400',
fontSize: rpx(28),
color: '#3D3D3D',
},
headerRight: {
flexDirection: 'row',
alignItems: 'center',
},
code: {
width: rpx(40),
height: rpx(40),
},
tit: {
marginTop: rpx(40),
fontWeight: '700',
fontSize: rpx(36),
color: '#3D3D3D',
},
content: {
marginTop: rpx(24),
width: rpx(688),
padding: rpx(42),
backgroundColor: '#FFFFFF',
borderRadius: rpx(30),
},
item: {
width: '100%',
paddingVertical: rpx(34),
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: '#D8D8D8',
},
lastItem: {
borderBottomWidth: 0,
},
itemIcon: {
width: rpx(38),
height: rpx(38),
marginTop: rpx(4),
marginRight: rpx(34),
},
itemText: {
fontWeight: '400',
fontSize: rpx(32),
color: '#3D3D3D',
},
tabBarContainer: {
marginLeft: rpx(-32),
}
});
export default ProfileScreen;

View File

@ -1,225 +1,311 @@
import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Image, TouchableOpacity } from 'react-native';
import React, { useState, useEffect } from 'react';
import { View, StyleSheet, ScrollView, Image, TouchableOpacity, SafeAreaView } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import {
TopNavigation,
TopNavigationAction,
Icon,
Text,
Layout,
Spinner
} from '@ui-kitten/components';
import { rpx } from '../../utils/rpx';
import { apiService } from '../../utils/api';
type CarItem = {
id: number;
title: string;
sn: string;
vehicleNum: string;
model: string;
status: string;
isShared: boolean;
remainingPower: number;
remainingMileage: number;
isDefault: number;
};
const BackIcon = (props: any) => (
<Icon {...props} name='arrow-back-outline'/>
);
export default function DeviceList() {
const navigation = useNavigation();
const [selectedItem, setSelectedItem] = useState<number | null>(null);
const handlePress = () => {
navigation.navigate('BindIndex' as never);
// console.log(navigation);
};
const [devices, setDevices] = useState<CarItem[]>([]);
const [loading, setLoading] = useState(true);
const handleCancel = () => {
navigation.goBack();
};
useEffect(() => {
fetchDeviceList();
}, []);
// 示例数据保持不变
const carList: CarItem[] = [
{
id: 1,
title: '备注1376',
model: '车型:飞过的魔毯不须归于温暖',
status: '车辆登入X小时出租',
isShared: false,
},
{
id: 2,
title: '备注1376',
model: '车型:飞过的魔毯不须归于温暖',
status: '车辆登入X小时出租',
isShared: true,
},
];
const toggleSelect = (id: number) => {
setSelectedItem(prev => prev === id ? null : id);
const fetchDeviceList = async () => {
try {
setLoading(true);
const response = await apiService.getDeviceList();
if (response?.code === 200 && response.data) {
setDevices(response.data);
}
} catch (error) {
console.error('获取设备列表失败:', error);
} finally {
setLoading(false);
}
};
const renderCarItem = (item: CarItem) => (
<View key={item.id} style={[styles.carItem, item.id !== 1 && styles.marginTop]}>
const handlePress = () => {
navigation.navigate('BindIndex' as never);
};
const handleCancel = () => {
navigation.goBack();
};
const selectCar = async (item: CarItem) => {
try {
const response = await apiService.toggleDefault(item.sn);
if (response.code === 200) {
console.log(response,'response');
fetchDeviceList();
// TODO: 添加成功提示
}
} catch (error) {
console.error('切换默认车辆失败:', error);
}
};
const renderBackAction = () => (
<TopNavigationAction icon={BackIcon} onPress={handleCancel}/>
);
const renderPowerIcons = (power: number) => {
const icons = [];
const fullPowerIcon = 'https://lxnapi.ccttiot.com/bike/img/static/uhxmJlps8lrRRTmBIFpl';
const emptyPowerIcon = 'https://lxnapi.ccttiot.com/bike/img/static/u1CcbtQydd107cOUEZ1l';
const powerPerGrid = power / 10;
for (let i = 0; i < 10; i++) {
icons.push(
<Image
key={`power-${i}`}
source={{ uri: i < Math.floor(powerPerGrid) ? fullPowerIcon : emptyPowerIcon }}
style={styles.powerIcon}
/>
);
}
return icons;
};
const renderCarItem = (item: CarItem, index: number) => (
<TouchableOpacity
key={`device-${item.sn}-${index}`}
style={[styles.carItem, index !== 0 && styles.marginTop]}
onPress={() => selectCar(item)}
>
<View style={styles.leftContent}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle}>{item.model}</Text>
<Text style={styles.status}>{item.status}</Text>
<Text style={[styles.tag, item.isShared && styles.tagShare]}>
{item.isShared ? '临时共享' : '车主'}
</Text>
<View style={styles.carInfo}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uUIunSd0CSU3ovogLJHk' }}
style={styles.titleIcon}
/>
<View style={styles.power}>
{renderPowerIcons(item.remainingPower)}
</View>
<Text style={styles.mileage}>{item.remainingMileage}KM</Text>
</View>
<Text style={styles.model}>{item.model}</Text>
<Text style={styles.carNum}>{item.vehicleNum}</Text>
<Text style={styles.status}></Text>
</View>
<View style={styles.rightContent}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uVnIDwcwQP7oo12PeYVJ' }}
style={styles.carImage}
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uB1F5aibooguILH8uB4F' }}
style={styles.carImage}
/>
<View style={styles.checkboxWrapper}>
<Text style={styles.checkboxText}></Text>
<TouchableOpacity
onPress={() => toggleSelect(item.id)}
style={styles.checkboxContainer}
>
<Image
source={{
uri: selectedItem === item.id
? 'https://api.ccttiot.com/smartmeter/img/static/u4alnzo5240dlVnSQK0r'
: 'https://api.ccttiot.com/smartmeter/img/static/uj2puOsyrcZY4ygZL6GX'
}}
style={styles.checkboxImage}
/>
</TouchableOpacity>
<Image
source={{
uri: item.isDefault == 1
? 'https://lxnapi.ccttiot.com/bike/img/static/uj7bE7GMcCm6g2igeo7k'
: 'https://lxnapi.ccttiot.com/bike/img/static/uN3QtzNuM2CMTLUm3joR'
}}
style={styles.checkboxImage}
/>
</View>
</View>
</View>
</TouchableOpacity>
);
return (
<View style={styles.container}>
<ScrollView style={styles.scrollContent}>
{carList.map(item => renderCarItem(item))}
</ScrollView>
<SafeAreaView style={styles.container}>
<TopNavigation
title="选择车辆"
alignment="center"
accessoryLeft={renderBackAction}
style={styles.topNav}
/>
{loading ? (
<View style={styles.loadingContainer}>
<Spinner size="large"/>
</View>
) : (
<>
<ScrollView style={styles.scrollContent}>
{devices.length > 0 ? (
devices.map((device, index) => renderCarItem(device, index))
) : (
<View style={styles.noDataContainer}>
<Text category="s1"></Text>
</View>
)}
</ScrollView>
<View style={styles.bottomButtons}>
<TouchableOpacity onPress={handlePress} style={styles.addButton}>
<Text style={styles.addButtonText}>+ </Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleCancel} style={styles.cancelButton}>
<Text style={styles.cancelButtonText}></Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.bottomButtons}>
<TouchableOpacity onPress={handlePress} style={styles.addButton}>
<Text style={styles.addButtonText}>+ </Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleCancel} style={styles.cancelButton}>
<Text style={styles.cancelButtonText}></Text>
</TouchableOpacity>
</View>
</>
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F3FCFF',
backgroundColor: '#fff',
},
topNav: {
backgroundColor: '#fff',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
noDataContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: rpx(100),
},
scrollContent: {
marginBottom: rpx(300),
flex: 1,
paddingHorizontal: rpx(20),
// marginBottom: rpx(160),
paddingHorizontal: rpx(40),
},
carItem: {
flexDirection: 'row',
backgroundColor: '#EEF2FD',
borderRadius: rpx(20),
padding: rpx(30),
borderRadius: rpx(28),
padding: rpx(48),
marginTop: rpx(20),
alignItems: 'center',
},
marginTop: {
marginTop: rpx(20),
},
leftContent: {
flex: 1,
justifyContent: 'center',
},
rightContent: {
carInfo: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginLeft: rpx(20),
},
title: {
fontSize: rpx(40),
fontWeight: '500',
color: '#3D3D3D',
marginBottom: rpx(16),
titleIcon: {
width: rpx(30),
height: rpx(30),
marginRight: rpx(12),
marginBottom: rpx(8),
},
subTitle: {
fontWeight: '500',
fontSize: rpx(24),
power: {
flexDirection: 'row',
alignItems: 'center',
marginRight: rpx(12),
},
powerIcon: {
width: rpx(18),
height: rpx(36),
},
mileage: {
fontSize: rpx(36),
fontWeight: '700',
color: '#3D3D3D',
},
model: {
marginTop: rpx(15),
fontSize: rpx(32),
fontWeight: '700',
color: '#3D3D3D',
},
carNum: {
marginTop: rpx(15),
fontSize: rpx(32),
fontWeight: '700',
color: '#3D3D3D',
marginBottom: rpx(12),
},
status: {
fontSize: rpx(24),
color: '#666',
marginBottom: rpx(16),
},
tag: {
alignSelf: 'flex-start',
paddingVertical: rpx(6),
paddingHorizontal: rpx(30),
marginTop: rpx(20),
paddingVertical: rpx(8),
paddingHorizontal: rpx(19),
backgroundColor: '#D2E8FF',
borderRadius: rpx(29),
fontSize: rpx(24),
fontSize: rpx(28),
color: '#4297F3',
marginTop: rpx(10),
fontWeight: '500'
alignSelf: 'flex-start',
fontWeight: '500',
},
tagShare: {
color: '#FF9500',
backgroundColor: '#FFEEDE'
rightContent: {
marginLeft: 'auto',
width: rpx(200),
alignItems: 'center',
},
carImage: {
width: rpx(232),
height: rpx(180),
marginBottom: rpx(16),
},
checkboxContainer: {
width: rpx(44),
height: rpx(44),
justifyContent: 'center',
alignItems: 'center',
width: rpx(212),
height: rpx(164),
},
checkboxWrapper: {
flexDirection: 'row',
alignItems: 'center',
marginTop: rpx(22),
},
checkboxText: {
fontSize: rpx(32),
checkboxText: {
fontSize: rpx(30),
color: '#3D3D3D',
marginRight: rpx(8),
marginRight: rpx(12),
},
checkboxImage: {
width: rpx(44),
height: rpx(44),
resizeMode: 'contain',
width: rpx(29),
height: rpx(29),
},
bottomButtons: {
position: 'absolute',
bottom: rpx(30),
left: 0,
right: 0,
padding: rpx(20),
backgroundColor: '#F3FCFF',
padding: rpx(38),
},
addButton: {
backgroundColor: '#4297F3',
height: rpx(92),
borderRadius: rpx(20),
backgroundColor: '#4297F3',
borderRadius: rpx(16),
justifyContent: 'center',
alignItems: 'center',
marginBottom: rpx(20),
},
addButtonText: {
fontSize: rpx(40),
fontWeight: '500',
color: '#FFFFFF',
fontSize: rpx(32),
},
cancelButton: {
backgroundColor: '#fff',
height: rpx(92),
borderRadius: rpx(20),
backgroundColor: '#fff',
borderRadius: rpx(16),
borderWidth: rpx(2),
borderColor: '#4297F3',
justifyContent: 'center',
alignItems: 'center',
},
cancelButtonText: {
fontSize: rpx(40),
fontWeight: '500',
color: '#4297F3',
fontSize: rpx(32),
},
});

View File

@ -0,0 +1,46 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { TopNavigation, TopNavigationAction, Icon, Text } from '@ui-kitten/components';
import { useNavigation } from '@react-navigation/native';
const BackIcon = (props) => (
<Icon {...props} name='arrow-back' />
);
const TestBule = () => {
const navigation = useNavigation();
const navigateBack = () => {
navigation.goBack();
};
const BackAction = () => (
<TopNavigationAction icon={BackIcon} onPress={navigateBack} />
);
return (
<View style={styles.container}>
<TopNavigation
title="蓝牙测试"
alignment="center"
accessoryLeft={BackAction}
/>
<View style={styles.content}>
<Text>TestBule</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default TestBule;