二维码保存

This commit is contained in:
tx 2024-12-26 16:17:19 +08:00
parent e184055bee
commit a0f361a9ff
4 changed files with 389 additions and 18089 deletions

17487
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
import React, { useEffect, useState, useCallback } from 'react';
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 { rpx } from '../../utils/rpx';
interface KeyItem {
id: string;
avatarUrl: string;
@ -18,23 +19,20 @@ const DeviceShare = () => {
const getKeyList = async () => {
const response = await apiService.getKeyListByOwnerId();
// console.log(response);
if (response.code == 200) {
setKeyList(response.data);
}
};
const calculateRemainingTime = (expirationTime: string): string => {
const now = new Date();
const expiration = new Date(expirationTime);
const diffTime = expiration.getTime() - now.getTime();
// 如果已过期
if (diffTime <= 0) {
return '已过期';
}
// 计算剩余天数、小时、分钟
const days = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const hours = Math.floor((diffTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diffTime % (1000 * 60 * 60)) / (1000 * 60));
@ -47,6 +45,7 @@ const DeviceShare = () => {
return `${minutes}分钟`;
}
};
const getStatusText = (status: string | number) => {
switch (status) {
case '0':
@ -59,6 +58,7 @@ const DeviceShare = () => {
return '未知状态';
}
};
useFocusEffect(
useCallback(() => {
getKeyList();
@ -72,14 +72,13 @@ const DeviceShare = () => {
<Text style={styles.shareTxt}>使</Text>
{keyList.map((key) => (
<TouchableOpacity
key={key.id}
onPress={() => {
navigation.navigate('ShareDetailScreen', { keyItem: key });
}}
>
<View key={key.id} style={styles.card}>
<TouchableOpacity
key={key.id}
onPress={() => {
navigation.navigate('ShareDetailScreen', { keyItem: key });
}}
>
<View style={styles.card}>
<View style={styles.cardTop}>
<Image
source={{ uri: key.avatarUrl || 'https://lxnapi.ccttiot.com/bike/img/static/uVnIDwcwQP7oo12PeYVJ' }}
@ -107,8 +106,8 @@ const DeviceShare = () => {
</View>
</View>
</TouchableOpacity>
))}
{keyList.length < 3 && (
<TouchableOpacity onPress={() => {
navigation.navigate('AddShare' as never);
@ -124,21 +123,20 @@ const DeviceShare = () => {
)}
</View>
<TouchableOpacity onPress={() => {
navigation.navigate('ExpiredKeysScreen' as never );
navigation.navigate('ExpiredKeysScreen' as never);
}}>
<View style={styles.shareTip}>
<Text style={styles.shareBtnTxt}></Text>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uGq4yJlU1ZZRkwiJ8Y74' }}
style={styles.lasttimeImg}
/>
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uGq4yJlU1ZZRkwiJ8Y74' }}
style={styles.lasttimeImg}
/>
</View>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
@ -205,7 +203,6 @@ const styles = StyleSheet.create({
},
cardTopTxt: {
marginLeft: rpx(28),
},
cardTopImg: {
width: rpx(96),
@ -213,7 +210,6 @@ const styles = StyleSheet.create({
borderRadius: rpx(48),
},
cardTopName: {
fontSize: rpx(44),
color: '#3D3D3D',
},
@ -221,7 +217,6 @@ const styles = StyleSheet.create({
fontSize: rpx(36),
color: '#808080',
},
shareBox: {
width: rpx(688),
borderRadius: rpx(30),
@ -245,8 +240,8 @@ const styles = StyleSheet.create({
marginTop: rpx(32),
borderRadius: rpx(16),
borderWidth: rpx(2),
borderColor: '#808080 ',
borderColor: '#808080',
},
});
export default DeviceShare;
export default DeviceShare;

View File

@ -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 { rpx } from '../../utils/rpx';
import { RouteProp, useRoute } from '@react-navigation/native';
import RNFS from 'react-native-fs';
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { Spinner, Layout } from '@ui-kitten/components';
import QRCode from 'react-native-qrcode-svg';
import { captureRef } from 'react-native-view-shot';
const ShareQrcode = () => {
const route = useRoute<RouteProp<RootStackParamList, 'ShareQrcode'>>();
const { QrId } = route.params;
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(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 2000);
}, 1000);
return () => clearTimeout(timer);
}, []);
const saveToGallery = async () => {
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) {
try {
let directoryPath = `${RNFS.DownloadDirectoryPath}`;
const fileName = `qrcode_${Date.now()}.jpg`;
let downloadDest = `${directoryPath}/${fileName}`;
const readPermission = await request(
Platform.select({
android: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
default: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
})
);
// 检查目录是否存在
let exists = await RNFS.exists(directoryPath);
if (!exists) {
try {
await RNFS.mkdir(directoryPath);
} catch (mkdirError) {
// 使用备用路径
directoryPath = `${RNFS.ExternalStorageDirectoryPath}`;
downloadDest = `${directoryPath}/${fileName}`;
}
}
// 下载文件
const options = {
fromUrl: qrCodeUrl,
toFile: downloadDest,
};
const response = await RNFS.downloadFile(options).promise;
if (response.statusCode === 200) {
const fileExists = await RNFS.exists(downloadDest);
if (fileExists) {
// 刷新媒体库
NativeModules.MediaScanner.scanFile(downloadDest, 'image/jpeg', (err) => {
if (err) {
Alert.alert('错误', '无法刷新媒体库');
} else {
Alert.alert('成功', '二维码已保存到相册');
if (writePermission !== RESULTS.GRANTED || readPermission !== RESULTS.GRANTED) {
Alert.alert(
'权限不足',
'请在设置中授予存储权限',
[
{ text: '取消' },
{
text: '去设置',
onPress: () => {
if (Platform.OS === 'android') {
Linking.openSettings();
}
});
} else {
throw new Error('文件未能成功创建');
}
}
} else {
Alert.alert('错误', '下载失败,请检查网络连接');
}
} catch (err) {
Alert.alert('错误', '保存失败,无法访问存储空间');
}
} else {
Alert.alert('权限不足', '请在设置中授予存储权限');
]
);
return;
}
// 使用应用专用目录
const fileName = `qrcode_${Date.now()}.jpg`;
const downloadDest = `${RNFS.ExternalDirectoryPath}/${fileName}`;
// 截取QR码视图
const uri = await captureRef(qrRef, {
format: 'jpg',
quality: 1,
});
// 复制文件到目标位置
await RNFS.copyFile(uri, downloadDest);
// 使用 MediaScanner 使图片在相册中可见
NativeModules.MediaScanner.scanFile(downloadDest, 'image/jpeg', (err) => {
if (err) {
console.error('MediaScanner error:', err);
Alert.alert('错误', '保存失败,请重试');
} else {
Alert.alert('成功', '二维码已保存到相册');
}
});
// 清理临时文件
try {
await RNFS.unlink(uri);
} catch (e) {
console.log('清理临时文件失败:', e);
}
} catch (error) {
console.error('Save error:', error);
Alert.alert('错误', '保存失败,请重试');
}
};
if (loading) {
return (
<Layout style={styles.loadingContainer}>
<Spinner size='giant' />
<Text style={styles.loadingText}>...</Text>
<Text style={styles.loadingText}>...</Text>
</Layout>
);
}
@ -97,10 +114,12 @@ const ShareQrcode = () => {
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u8yqVl9y6fcU3fTDDTpK' }}
style={styles.qrBack}
>
<Layout style={styles.qrContainer}>
<Image
source={{ uri: qrCodeUrl }}
style={styles.qrCode}
<Layout style={styles.qrContainer} ref={qrRef}>
<QRCode
value={qrCodeUrl}
size={rpx(438)}
color="black"
backgroundColor="white"
/>
</Layout>
</ImageBackground>
@ -147,19 +166,20 @@ const styles = StyleSheet.create({
width: rpx(614),
height: rpx(368),
},
qrCode:{
width: rpx(438),
height: rpx(438),
},
qrContainer: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
padding: rpx(20),
borderRadius: rpx(10),
},
qrBack: {
marginTop: rpx(-20),
width: rpx(614),
height: rpx(614),
padding: rpx(86),
alignItems: 'center',
justifyContent: 'center',
},
backimg: {
width: rpx(688),

806
yarn.lock

File diff suppressed because it is too large Load Diff