二维码保存
This commit is contained in:
parent
e184055bee
commit
a0f361a9ff
17487
package-lock.json
generated
17487
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,9 @@
|
||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
|
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
|
||||||
import { useNavigation ,useFocusEffect} from '@react-navigation/native';
|
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||||
import { apiService } from '../../utils/api';
|
import { apiService } from '../../utils/api';
|
||||||
import { rpx } from '../../utils/rpx';
|
import { rpx } from '../../utils/rpx';
|
||||||
|
|
||||||
interface KeyItem {
|
interface KeyItem {
|
||||||
id: string;
|
id: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
|
@ -18,23 +19,20 @@ const DeviceShare = () => {
|
||||||
|
|
||||||
const getKeyList = async () => {
|
const getKeyList = async () => {
|
||||||
const response = await apiService.getKeyListByOwnerId();
|
const response = await apiService.getKeyListByOwnerId();
|
||||||
// console.log(response);
|
|
||||||
if (response.code == 200) {
|
if (response.code == 200) {
|
||||||
setKeyList(response.data);
|
setKeyList(response.data);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateRemainingTime = (expirationTime: string): string => {
|
const calculateRemainingTime = (expirationTime: string): string => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const expiration = new Date(expirationTime);
|
const expiration = new Date(expirationTime);
|
||||||
const diffTime = expiration.getTime() - now.getTime();
|
const diffTime = expiration.getTime() - now.getTime();
|
||||||
|
|
||||||
// 如果已过期
|
|
||||||
if (diffTime <= 0) {
|
if (diffTime <= 0) {
|
||||||
return '已过期';
|
return '已过期';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算剩余天数、小时、分钟
|
|
||||||
const days = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
const days = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||||
const hours = Math.floor((diffTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
const hours = Math.floor((diffTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
const minutes = Math.floor((diffTime % (1000 * 60 * 60)) / (1000 * 60));
|
const minutes = Math.floor((diffTime % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
@ -47,6 +45,7 @@ const DeviceShare = () => {
|
||||||
return `${minutes}分钟`;
|
return `${minutes}分钟`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusText = (status: string | number) => {
|
const getStatusText = (status: string | number) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case '0':
|
case '0':
|
||||||
|
@ -59,6 +58,7 @@ const DeviceShare = () => {
|
||||||
return '未知状态';
|
return '未知状态';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
getKeyList();
|
getKeyList();
|
||||||
|
@ -78,8 +78,7 @@ const DeviceShare = () => {
|
||||||
navigation.navigate('ShareDetailScreen', { keyItem: key });
|
navigation.navigate('ShareDetailScreen', { keyItem: key });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<View style={styles.card}>
|
||||||
<View key={key.id} style={styles.card}>
|
|
||||||
<View style={styles.cardTop}>
|
<View style={styles.cardTop}>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: key.avatarUrl || 'https://lxnapi.ccttiot.com/bike/img/static/uVnIDwcwQP7oo12PeYVJ' }}
|
source={{ uri: key.avatarUrl || 'https://lxnapi.ccttiot.com/bike/img/static/uVnIDwcwQP7oo12PeYVJ' }}
|
||||||
|
@ -107,8 +106,8 @@ const DeviceShare = () => {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{keyList.length < 3 && (
|
{keyList.length < 3 && (
|
||||||
<TouchableOpacity onPress={() => {
|
<TouchableOpacity onPress={() => {
|
||||||
navigation.navigate('AddShare' as never);
|
navigation.navigate('AddShare' as never);
|
||||||
|
@ -124,7 +123,7 @@ const DeviceShare = () => {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity onPress={() => {
|
<TouchableOpacity onPress={() => {
|
||||||
navigation.navigate('ExpiredKeysScreen' as never );
|
navigation.navigate('ExpiredKeysScreen' as never);
|
||||||
}}>
|
}}>
|
||||||
<View style={styles.shareTip}>
|
<View style={styles.shareTip}>
|
||||||
<Text style={styles.shareBtnTxt}>查看已失效共享</Text>
|
<Text style={styles.shareBtnTxt}>查看已失效共享</Text>
|
||||||
|
@ -138,7 +137,6 @@ const DeviceShare = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
@ -205,7 +203,6 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
cardTopTxt: {
|
cardTopTxt: {
|
||||||
marginLeft: rpx(28),
|
marginLeft: rpx(28),
|
||||||
|
|
||||||
},
|
},
|
||||||
cardTopImg: {
|
cardTopImg: {
|
||||||
width: rpx(96),
|
width: rpx(96),
|
||||||
|
@ -213,7 +210,6 @@ const styles = StyleSheet.create({
|
||||||
borderRadius: rpx(48),
|
borderRadius: rpx(48),
|
||||||
},
|
},
|
||||||
cardTopName: {
|
cardTopName: {
|
||||||
|
|
||||||
fontSize: rpx(44),
|
fontSize: rpx(44),
|
||||||
color: '#3D3D3D',
|
color: '#3D3D3D',
|
||||||
},
|
},
|
||||||
|
@ -221,7 +217,6 @@ const styles = StyleSheet.create({
|
||||||
fontSize: rpx(36),
|
fontSize: rpx(36),
|
||||||
color: '#808080',
|
color: '#808080',
|
||||||
},
|
},
|
||||||
|
|
||||||
shareBox: {
|
shareBox: {
|
||||||
width: rpx(688),
|
width: rpx(688),
|
||||||
borderRadius: rpx(30),
|
borderRadius: rpx(30),
|
||||||
|
@ -245,8 +240,8 @@ const styles = StyleSheet.create({
|
||||||
marginTop: rpx(32),
|
marginTop: rpx(32),
|
||||||
borderRadius: rpx(16),
|
borderRadius: rpx(16),
|
||||||
borderWidth: rpx(2),
|
borderWidth: rpx(2),
|
||||||
borderColor: '#808080 ',
|
borderColor: '#808080',
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default DeviceShare;
|
export default DeviceShare;
|
|
@ -1,88 +1,105 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { View, StyleSheet, Image, ImageBackground, TouchableOpacity, Text, Alert, Platform, NativeModules } from 'react-native';
|
import { View, StyleSheet, Image, ImageBackground, TouchableOpacity, Text, Alert, Platform, NativeModules } from 'react-native';
|
||||||
import { rpx } from '../../utils/rpx';
|
import { rpx } from '../../utils/rpx';
|
||||||
import { RouteProp, useRoute } from '@react-navigation/native';
|
import { RouteProp, useRoute } from '@react-navigation/native';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
||||||
import { Spinner, Layout } from '@ui-kitten/components';
|
import { Spinner, Layout } from '@ui-kitten/components';
|
||||||
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
|
import { captureRef } from 'react-native-view-shot';
|
||||||
|
|
||||||
const ShareQrcode = () => {
|
const ShareQrcode = () => {
|
||||||
const route = useRoute<RouteProp<RootStackParamList, 'ShareQrcode'>>();
|
const route = useRoute<RouteProp<RootStackParamList, 'ShareQrcode'>>();
|
||||||
const { QrId } = route.params;
|
const { QrId } = route.params;
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${rpx(400)}x${rpx(400)}&data=https://testlu.chuangtewl.com/prod-api?keyId=${QrId}`;
|
const qrCodeUrl = `https://testlu.chuangtewl.com/prod-api?keyId=${QrId}`;
|
||||||
|
const qrRef = useRef();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, 2000);
|
}, 1000);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const saveToGallery = async () => {
|
const saveToGallery = async () => {
|
||||||
try {
|
try {
|
||||||
const writePermission = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
|
// 修正权限请求
|
||||||
const readPermission = await request(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE);
|
const writePermission = await request(
|
||||||
|
Platform.select({
|
||||||
|
android: PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE,
|
||||||
|
default: PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (writePermission === RESULTS.GRANTED && readPermission === RESULTS.GRANTED) {
|
const readPermission = await request(
|
||||||
try {
|
Platform.select({
|
||||||
let directoryPath = `${RNFS.DownloadDirectoryPath}`;
|
android: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
|
||||||
|
default: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (writePermission !== RESULTS.GRANTED || readPermission !== RESULTS.GRANTED) {
|
||||||
|
Alert.alert(
|
||||||
|
'权限不足',
|
||||||
|
'请在设置中授予存储权限',
|
||||||
|
[
|
||||||
|
{ text: '取消' },
|
||||||
|
{
|
||||||
|
text: '去设置',
|
||||||
|
onPress: () => {
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
Linking.openSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用应用专用目录
|
||||||
const fileName = `qrcode_${Date.now()}.jpg`;
|
const fileName = `qrcode_${Date.now()}.jpg`;
|
||||||
let downloadDest = `${directoryPath}/${fileName}`;
|
const downloadDest = `${RNFS.ExternalDirectoryPath}/${fileName}`;
|
||||||
|
|
||||||
// 检查目录是否存在
|
// 截取QR码视图
|
||||||
let exists = await RNFS.exists(directoryPath);
|
const uri = await captureRef(qrRef, {
|
||||||
if (!exists) {
|
format: 'jpg',
|
||||||
try {
|
quality: 1,
|
||||||
await RNFS.mkdir(directoryPath);
|
});
|
||||||
} catch (mkdirError) {
|
|
||||||
// 使用备用路径
|
|
||||||
directoryPath = `${RNFS.ExternalStorageDirectoryPath}`;
|
|
||||||
downloadDest = `${directoryPath}/${fileName}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载文件
|
// 复制文件到目标位置
|
||||||
const options = {
|
await RNFS.copyFile(uri, downloadDest);
|
||||||
fromUrl: qrCodeUrl,
|
|
||||||
toFile: downloadDest,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await RNFS.downloadFile(options).promise;
|
// 使用 MediaScanner 使图片在相册中可见
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
const fileExists = await RNFS.exists(downloadDest);
|
|
||||||
if (fileExists) {
|
|
||||||
// 刷新媒体库
|
|
||||||
NativeModules.MediaScanner.scanFile(downloadDest, 'image/jpeg', (err) => {
|
NativeModules.MediaScanner.scanFile(downloadDest, 'image/jpeg', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
Alert.alert('错误', '无法刷新媒体库');
|
console.error('MediaScanner error:', err);
|
||||||
|
Alert.alert('错误', '保存失败,请重试');
|
||||||
} else {
|
} else {
|
||||||
Alert.alert('成功', '二维码已保存到相册');
|
Alert.alert('成功', '二维码已保存到相册');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
throw new Error('文件未能成功创建');
|
// 清理临时文件
|
||||||
}
|
try {
|
||||||
} else {
|
await RNFS.unlink(uri);
|
||||||
Alert.alert('错误', '下载失败,请检查网络连接');
|
} catch (e) {
|
||||||
}
|
console.log('清理临时文件失败:', e);
|
||||||
} catch (err) {
|
|
||||||
Alert.alert('错误', '保存失败,无法访问存储空间');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Alert.alert('权限不足', '请在设置中授予存储权限');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Save error:', error);
|
||||||
Alert.alert('错误', '保存失败,请重试');
|
Alert.alert('错误', '保存失败,请重试');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Layout style={styles.loadingContainer}>
|
<Layout style={styles.loadingContainer}>
|
||||||
<Spinner size='giant' />
|
<Spinner size='giant' />
|
||||||
<Text style={styles.loadingText}>加载中...</Text>
|
<Text style={styles.loadingText}>二维码生成中...</Text>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -97,10 +114,12 @@ const ShareQrcode = () => {
|
||||||
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u8yqVl9y6fcU3fTDDTpK' }}
|
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u8yqVl9y6fcU3fTDDTpK' }}
|
||||||
style={styles.qrBack}
|
style={styles.qrBack}
|
||||||
>
|
>
|
||||||
<Layout style={styles.qrContainer}>
|
<Layout style={styles.qrContainer} ref={qrRef}>
|
||||||
<Image
|
<QRCode
|
||||||
source={{ uri: qrCodeUrl }}
|
value={qrCodeUrl}
|
||||||
style={styles.qrCode}
|
size={rpx(438)}
|
||||||
|
color="black"
|
||||||
|
backgroundColor="white"
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
</ImageBackground>
|
</ImageBackground>
|
||||||
|
@ -147,19 +166,20 @@ const styles = StyleSheet.create({
|
||||||
width: rpx(614),
|
width: rpx(614),
|
||||||
height: rpx(368),
|
height: rpx(368),
|
||||||
},
|
},
|
||||||
qrCode:{
|
|
||||||
width: rpx(438),
|
|
||||||
height: rpx(438),
|
|
||||||
},
|
|
||||||
qrContainer: {
|
qrContainer: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
padding: rpx(20),
|
||||||
|
borderRadius: rpx(10),
|
||||||
},
|
},
|
||||||
qrBack: {
|
qrBack: {
|
||||||
marginTop: rpx(-20),
|
marginTop: rpx(-20),
|
||||||
width: rpx(614),
|
width: rpx(614),
|
||||||
height: rpx(614),
|
height: rpx(614),
|
||||||
padding: rpx(86),
|
padding: rpx(86),
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
backimg: {
|
backimg: {
|
||||||
width: rpx(688),
|
width: rpx(688),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user