From 05872f47d7fa4a78e3007d1b8215205cc4664af4 Mon Sep 17 00:00:00 2001
From: tx <2622874537@qq.com>
Date: Thu, 26 Dec 2024 14:12:13 +0800
Subject: [PATCH] =?UTF-8?q?=E9=92=A5=E5=8C=99=E5=85=B1=E4=BA=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
android/app/src/main/AndroidManifest.xml | 2 +
package-lock.json | 22 +
package.json | 1 +
src/navigation/HomeStack.tsx | 8 +
src/navigation/types.tsx | 1 +
src/utils/api.ts | 3 +
src/views/device/DeviceShare.tsx | 27 +-
src/views/device/ExpiredKeysScreen.tsx | 106 +++++
src/views/device/KeyDetail.tsx | 536 ++++++++++++++++-------
src/views/device/shareQrcode.tsx | 170 ++++---
yarn.lock | 22 +-
11 files changed, 657 insertions(+), 241 deletions(-)
create mode 100644 src/views/device/ExpiredKeysScreen.tsx
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b78688e..a28bc4d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -19,6 +19,8 @@
+
+
=0.60.0"
}
},
+ "node_modules/react-native-qrcode-svg": {
+ "version": "6.3.12",
+ "resolved": "https://registry.npmmirror.com/react-native-qrcode-svg/-/react-native-qrcode-svg-6.3.12.tgz",
+ "integrity": "sha512-7Bx23ZdFNJJdVXyW9BJmFWdI5kccjnpotzmL3exkV0irUKTmj51jesxpn5sqtgVdYFE4IUVoGzdS+8qg6Ua9BA==",
+ "dependencies": {
+ "prop-types": "^15.8.0",
+ "qrcode": "^1.5.1",
+ "text-encoding": "^0.7.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": ">=0.63.4",
+ "react-native-svg": ">=13.2.0"
+ }
+ },
"node_modules/react-native-reanimated": {
"version": "3.16.3",
"resolved": "https://registry.npmmirror.com/react-native-reanimated/-/react-native-reanimated-3.16.3.tgz",
@@ -16440,6 +16456,12 @@
"node": "*"
}
},
+ "node_modules/text-encoding": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmmirror.com/text-encoding/-/text-encoding-0.7.0.tgz",
+ "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==",
+ "deprecated": "no longer maintained"
+ },
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
diff --git a/package.json b/package.json
index 19708c8..d364818 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"react-native-linear-gradient": "^2.8.3",
"react-native-permissions": "^5.2.1",
"react-native-qrcode-scanner": "^1.5.5",
+ "react-native-qrcode-svg": "^6.3.12",
"react-native-reanimated": "^3.16.1",
"react-native-safe-area-context": "^4.14.0",
"react-native-screens": "^3.35.0",
diff --git a/src/navigation/HomeStack.tsx b/src/navigation/HomeStack.tsx
index b02db8d..3900489 100644
--- a/src/navigation/HomeStack.tsx
+++ b/src/navigation/HomeStack.tsx
@@ -16,6 +16,7 @@ import { getFocusedRouteNameFromRoute } from '@react-navigation/native';
import { HomeStackParamList } from './types';
import TestBule from '../views/device/test_bule';
import ShareDetailScreen from '../views/device/KeyDetail';
+import ExpiredKeysScreen from '../views/device/ExpiredKeysScreen';
const Stack = createStackNavigator();
const createScreenOptions = (title: string): StackNavigationOptions => {
@@ -84,6 +85,13 @@ export default function HomeStackNavigator({ navigation, route }: Props) {
component={SnBind}
options={createScreenOptions('手动绑车')}
/> */}
+
api.post(`/appVerify/admin/lock?sn=${sn}`),
getKeyListByOwnerId: () => api.get('/appVerify/getKeyListByOwnerId'),
addKey: (data: any) => api.post('/appVerify/addKey', data),
+ updateKey: (data: any) => api.put('/appVerify/editKey', data),
+ deleteKey: (key: any) => api.delete('/appVerify/del/' + key),
+ getExpiredKeys: () => api.get('/appVerify/getExpiredKeyListByOwnerId'),
};
\ No newline at end of file
diff --git a/src/views/device/DeviceShare.tsx b/src/views/device/DeviceShare.tsx
index 3a7f79f..c02f76c 100644
--- a/src/views/device/DeviceShare.tsx
+++ b/src/views/device/DeviceShare.tsx
@@ -1,6 +1,6 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
-import { useNavigation } from '@react-navigation/native';
+import { useNavigation ,useFocusEffect} from '@react-navigation/native';
import { apiService } from '../../utils/api';
import { rpx } from '../../utils/rpx';
interface KeyItem {
@@ -18,7 +18,7 @@ const DeviceShare = () => {
const getKeyList = async () => {
const response = await apiService.getKeyListByOwnerId();
-
+ // console.log(response);
if (response.code == 200) {
setKeyList(response.data);
@@ -59,9 +59,11 @@ const DeviceShare = () => {
return '未知状态';
}
};
- useEffect(() => {
- getKeyList();
- }, []);
+ useFocusEffect(
+ useCallback(() => {
+ getKeyList();
+ }, [])
+ );
return (
@@ -121,14 +123,17 @@ const DeviceShare = () => {
)}
-
-
- 查看已失效共享
- {
+ navigation.navigate('ExpiredKeysScreen' as never );
+ }}>
+
+ 查看已失效共享
+
-
+
+
);
};
diff --git a/src/views/device/ExpiredKeysScreen.tsx b/src/views/device/ExpiredKeysScreen.tsx
new file mode 100644
index 0000000..6e1574c
--- /dev/null
+++ b/src/views/device/ExpiredKeysScreen.tsx
@@ -0,0 +1,106 @@
+import React, { useEffect, useState } from 'react';
+import { ImageBackground, FlatList, StyleSheet, Image } from 'react-native';
+import { TopNavigation, TopNavigationAction, Icon, Card, Layout, Text } from '@ui-kitten/components';
+import { rpx } from '../../utils/rpx';
+import { apiService } from '../../utils/api';
+
+const BackIcon = (props) => (
+
+);
+
+const ExpiredKeysScreen = ({ navigation }) => {
+ const [data, setData] = useState([]);
+
+ const navigateBack = () => {
+ navigation.goBack();
+ };
+
+ const BackAction = () => (
+
+ );
+
+ const getExpiredKeys = async () => {
+ const res = await apiService.getExpiredKeys();
+ setData(res || []);
+ };
+
+ const renderItem = ({ item }) => (
+
+ {item.name} {item.number}
+ 失效时间:{item.date}
+
+ );
+
+ useEffect(() => {
+ getExpiredKeys();
+ }, []);
+
+ return (
+
+
+ {data.length > 0 ? (
+ item.id}
+ contentContainerStyle={styles.list}
+ />
+ ) : (
+
+ {/* */}
+ 暂无失效分享
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ background: {
+ flex: 1,
+ },
+ list: {
+ padding: rpx(30),
+ },
+ card: {
+ marginVertical: rpx(20),
+ padding: rpx(30),
+ borderRadius: rpx(20),
+ },
+ name: {
+ fontSize: rpx(36),
+ fontWeight: '700',
+ color: '#3D3D3D',
+ },
+ date: {
+ fontSize: rpx(28),
+ color: '#808080',
+ marginTop: rpx(10),
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: rpx(20),
+ },
+ emptyImage: {
+ width: rpx(200),
+ height: rpx(200),
+ marginBottom: rpx(20),
+ },
+ emptyText: {
+ color: '#808080',
+ },
+});
+
+export default ExpiredKeysScreen;
\ No newline at end of file
diff --git a/src/views/device/KeyDetail.tsx b/src/views/device/KeyDetail.tsx
index 8a8aa33..213abc6 100644
--- a/src/views/device/KeyDetail.tsx
+++ b/src/views/device/KeyDetail.tsx
@@ -1,179 +1,409 @@
import React from 'react';
-import { StyleSheet, View, Image,TouchableOpacity } from 'react-native';
-import { Layout, Text, Avatar, Button, Divider, TopNavigation, TopNavigationAction, Icon } from '@ui-kitten/components';
+import { StyleSheet, View, Image, TouchableOpacity, Modal } from 'react-native';
+import { Layout, Text, Avatar, Button, TopNavigation, TopNavigationAction, Icon, Card } from '@ui-kitten/components';
import { rpx } from '../../utils/rpx';
-import { useNavigation,useRoute } from '@react-navigation/native';
+import { useNavigation, useRoute } from '@react-navigation/native';
+import { apiService } from '../../utils/api';
+
interface KeyItem {
- id: string;
+ keyId: string;
avatarUrl: string;
shareUserName: string;
sharePhone: string;
status: string;
expirationTime: string;
}
+
const BackIcon = (props) => (
-
+
);
const KeyDetail = () => {
- const navigation = useNavigation();
+ const navigation = useNavigation();
+ const route = useRoute();
+ const keyItem = route.params?.keyItem as KeyItem;
+ const [visible, setVisible] = React.useState(false);
+ const [showTimeModal, setShowTimeModal] = React.useState(false);
+ const [selectedTime, setSelectedTime] = React.useState('');
+ const timeOptions = ['1天', '7天', '30天', '永久'];
- const route = useRoute();
- const keyItem = route.params?.keyItem as KeyItem;
-
-
- const navigateBack = () => {
- navigation.goBack();
- };
+ const showDeleteModal = () => {
+ setVisible(true);
+ };
- const BackAction = () => (
-
- );
+ const hideDeleteModal = () => {
+ setVisible(false);
- const RightIcon = (props) => (
-
- );
+ };
- return (
-
-
+ const handleDelete = () => {
+ // 这里添加删除逻辑
+ hideDeleteModal();
+ apiService.deleteKey(keyItem.keyId).then(res => {
+ console.log(res,'resresres');
+ if (res.code == 200) {
+ navigation.goBack();
+ }
+ });
+ };
-
- {
+ navigation.goBack();
+ };
+
+ const handleTimeSelect = (time: string) => {
+ setSelectedTime(time);
+ setShowTimeModal(false);
+ // 这里添加更新有效期的逻辑
+ };
+
+ const BackAction = () => (
+
- 小黄鸭
- 13569254567
- 剩余有效期:23小时54分钟
-
- 修改钥匙有效期
-
-
-
- 删除共享人
-
-
-
+ );
+ 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));
+
+ if (days > 0) {
+ return `${days}天${hours}小时`;
+ } else if (hours > 0) {
+ return `${hours}小时${minutes}分钟`;
+ } else {
+ return `${minutes}分钟`;
+ }
+ };
+ const RightIcon = (props: any) => (
+
+ );
+ const toQrCode = () => {
+ navigation.navigate('ShareQrcode', { QrId: keyItem.keyId });
+ };
+ return (
+
+
+
+
+
+ {keyItem.shareUserName}
+ {keyItem.sharePhone}
+ 剩余有效期:{calculateRemainingTime(keyItem.expirationTime)}
+ setShowTimeModal(true)}>
+
+ 修改钥匙有效期
+
+
+
+
+ 删除共享人
+
+
+
+
+
+
+
+ {/* 删除确认弹窗 */}
+
+
+
+ 删除后,被共享人将无法再操控 您的车辆,确定删除吗?
+
+
+
+
+
+
+
+
+ {/* 时间选择弹窗 */}
+ setShowTimeModal(false)}>
+
+
+
+ setShowTimeModal(false)}
+ >
+ 取消
+
+ 选择有效期
+ setShowTimeModal(false)}
+ >
+ 确定
+
+
+
+ {timeOptions.map((option, index) => (
+ handleTimeSelect(option)}
+ >
+
+ {option}
+
+
+ ))}
+
+
+
+
+
+ );
};
const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#F5F5F5',
- },
- topNav: {
- backgroundColor: '#fff',
- borderBottomWidth: 1,
- borderBottomColor: '#E8E8E8',
- },
- header: {
- alignItems: 'center',
- backgroundColor: '#fff',
- paddingVertical: rpx(40),
- marginTop: rpx(20),
- },
- avatar: {
- width: rpx(188),
- height: rpx(188),
- marginBottom: rpx(16),
- },
- name: {
- fontWeight: 'bold',
- fontSize: rpx(48),
- color: '#333',
- marginBottom: rpx(8),
- },
- deleteBtn:{
- marginTop: rpx(56),
-
- },
- deleteText:{
- fontWeight: 'bold',
- fontSize: rpx(40),
- color: '#FF8282',
- },
- eidtBtn: {
- width: rpx(592),
- marginTop: rpx(20),
- height: rpx(108),
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: rpx(16),
- backgroundColor: '#EEEFF9',
- borderRadius: rpx(16),
- },
- phone: {
-
- fontSize: rpx(36),
- color: '#808080',
- marginBottom: rpx(8),
- },
- expireTime: {
- marginTop: rpx(20),
- fontWeight: '400',
- fontSize: rpx(36),
- color: '#808080',
- },
- actionContainer: {
- backgroundColor: '#fff',
- marginTop: rpx(20),
- },
- actionButton: {
- justifyContent: 'space-between',
- paddingHorizontal: rpx(32),
- paddingVertical: rpx(24),
- backgroundColor: '#fff',
- borderRadius: 0,
- },
- buttonText: {
- fontSize: rpx(28),
- color: '#333',
- },
- arrowIcon: {
- width: rpx(38),
- height: rpx(38),
- tintColor: '#999',
- },
- deleteButton: {
- paddingVertical: rpx(24),
- backgroundColor: '#fff',
- borderRadius: 0,
- },
-
- divider: {
- backgroundColor: '#E8E8E8',
- },
+ container: {
+ width: rpx(750),
+ flex: 1,
+ backgroundColor: '#F3FCFF',
+ },
+ topNav: {
+ backgroundColor: '#F3FCFF',
+ borderBottomWidth: 1,
+ borderBottomColor: '#fff',
+ },
+ header: {
+ marginHorizontal: 'auto',
+ width: rpx(700),
+ alignItems: 'center',
+ backgroundColor: '#fff',
+ paddingVertical: rpx(40),
+ marginTop: rpx(20),
+ alignSelf: 'center',
+ borderRadius: rpx(16),
+ },
+ avatar: {
+ width: rpx(188),
+ height: rpx(188),
+ marginBottom: rpx(16),
+ },
+ name: {
+ fontWeight: 'bold',
+ fontSize: rpx(48),
+ color: '#333',
+ marginBottom: rpx(8),
+ },
+ deleteBtn: {
+ marginTop: rpx(56),
+ },
+ deleteText: {
+ fontWeight: 'bold',
+ fontSize: rpx(40),
+ color: '#FF8282',
+ },
+ eidtBtn: {
+ width: rpx(592),
+ marginTop: rpx(20),
+ height: rpx(108),
+ backgroundColor: '#EEEFF9',
+ borderRadius: rpx(16),
+ },
+ editBtnContent: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: rpx(16),
+ },
+ editBtnText: {
+ fontSize: rpx(36),
+ color: '#808080',
+ },
+ phone: {
+ fontSize: rpx(36),
+ color: '#808080',
+ marginBottom: rpx(8),
+ },
+ expireTime: {
+ marginTop: rpx(20),
+ fontWeight: '400',
+ fontSize: rpx(36),
+ color: '#808080',
+ },
+ actionContainer: {
+ marginHorizontal: 'auto',
+ width: rpx(700),
+ backgroundColor: '#fff',
+ marginTop: rpx(20),
+ },
+ actionButton: {
+ justifyContent: 'space-between',
+ paddingHorizontal: rpx(32),
+ paddingVertical: rpx(24),
+ backgroundColor: '#fff',
+ borderRadius: 0,
+ },
+ buttonText: {
+ fontSize: rpx(36),
+ color: '#3D3D3D',
+ },
+ arrowIcon: {
+ width: rpx(38),
+ height: rpx(38),
+ tintColor: '#999',
+ },
+ deleteButton: {
+ paddingVertical: rpx(24),
+ backgroundColor: '#fff',
+ borderRadius: 0,
+ },
+ divider: {
+ backgroundColor: '#E8E8E8',
+ },
+ modalOverlay: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ },
+ modalCard: {
+ width: rpx(600),
+ borderRadius: rpx(20),
+ padding: rpx(32),
+ backgroundColor: '#fff',
+ },
+ modalText: {
+ textAlign: 'center',
+ marginBottom: rpx(40),
+ fontSize: rpx(36),
+ fontWeight: '500',
+ color: '#333',
+ },
+ modalButtons: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginTop: rpx(20),
+ },
+ modalButton: {
+ flex: 1,
+ marginHorizontal: rpx(15),
+ borderRadius: rpx(16),
+ height: rpx(88),
+ },
+ modalContainer: {
+ flex: 1,
+ justifyContent: 'flex-end',
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ },
+ modalContent: {
+ backgroundColor: '#fff',
+ borderTopLeftRadius: rpx(20),
+ borderTopRightRadius: rpx(20),
+ paddingBottom: rpx(30),
+ },
+ modalHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ height: rpx(100),
+ borderBottomWidth: 1,
+ borderBottomColor: '#eee',
+ paddingHorizontal: rpx(30),
+ },
+ headerButton: {
+ padding: rpx(20),
+ },
+ modalTitle: {
+ fontSize: rpx(32),
+ fontWeight: '500',
+ color: '#333',
+ },
+ cancelText: {
+ fontSize: rpx(32),
+ color: '#666',
+ },
+ confirmText: {
+ fontSize: rpx(32),
+ color: '#4297F3',
+ },
+ optionsContainer: {
+ paddingHorizontal: rpx(30),
+ paddingTop: rpx(20),
+ alignItems: 'center',
+ },
+ optionItem: {
+ height: rpx(100),
+ width: rpx(600),
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderRadius: rpx(16),
+ marginBottom: rpx(20),
+ },
+ optionText: {
+ fontSize: rpx(32),
+ color: '#333',
+ },
+ selectedItemBg: {
+ backgroundColor: '#F0F7FF',
+ },
+ selectedOption: {
+ color: '#4297F3',
+ },
});
export default KeyDetail;
\ No newline at end of file
diff --git a/src/views/device/shareQrcode.tsx b/src/views/device/shareQrcode.tsx
index e076ce1..b0996c9 100644
--- a/src/views/device/shareQrcode.tsx
+++ b/src/views/device/shareQrcode.tsx
@@ -1,101 +1,118 @@
-import React from 'react';
+import React, { useState, useEffect } 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'; // 新增的库
const ShareQrcode = () => {
const route = useRoute>();
const { QrId } = route.params;
-
- const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${rpx(400)}x${rpx(400)}&data=https://your-domain.com/share?id=${QrId}`;
- console.log(qrCodeUrl);
-
- // const qrCodeUrl = `https://lxnapi.ccttiot.com/bike/img/static/upCrcuiBaoZrd9ZRZayj`
- // https://lxnapi.ccttiot.com/bike/img/static/upCrcuiBaoZrd9ZRZayj
+ const [loading, setLoading] = useState(true);
+ const [qrCodeRef, setQrCodeRef] = useState(null); // 用于保存二维码的引用
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setLoading(false);
+ }, 2000);
+
+ 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);
-
- if (writePermission === RESULTS.GRANTED && readPermission === RESULTS.GRANTED) {
- try {
- const directoryPath = `${RNFS.DownloadDirectoryPath}`;
- const fileName = `qrcode_${Date.now()}.jpg`;
- let downloadDest = `${directoryPath}/${fileName}`;
-
- // 检查目录是否存在
- const exists = await RNFS.exists(directoryPath);
- console.log('目录是否存在:', exists);
-
- if (!exists) {
- console.log('尝试创建目录:', directoryPath);
- try {
- await RNFS.mkdir(directoryPath);
- } catch (mkdirError) {
- console.error('创建目录失败:', mkdirError);
- // 如果创建目录失败,尝试直接使用根目录
- downloadDest = `${RNFS.ExternalStorageDirectoryPath}/${fileName}`;
- }
- }
-
- console.log('开始下载文件到:', downloadDest);
- const options = {
- fromUrl: qrCodeUrl,
- toFile: downloadDest,
- };
-
- const response = await RNFS.downloadFile(options).promise;
- console.log('下载响应:', response);
-
- if (response.statusCode === 200) {
- const fileExists = await RNFS.exists(downloadDest);
- if (fileExists) {
- // 使用原生模块刷新媒体库
- NativeModules.MediaScanner.scanFile(downloadDest, 'image/jpeg', (err) => {
- if (err) {
- console.error('媒体扫描错误:', err);
- Alert.alert('错误', '无法刷新媒体库');
- } else {
- Alert.alert('成功', '二维码已保存到相册');
- }
- });
- } else {
- throw new Error('文件未能成功创建');
- }
- } else {
- Alert.alert('错误', '下载失败,请检查网络连接');
- }
- } catch (err) {
- console.error('文件操作详细错误:', JSON.stringify(err));
- Alert.alert('错误', '保存失败,无法访问存储空间');
+ // 检查并请求权限
+ let permissionResult;
+ if (Platform.OS === 'android') {
+ if (Platform.Version >= 33) {
+ permissionResult = await request(PERMISSIONS.ANDROID.READ_MEDIA_IMAGES);
+ } else {
+ permissionResult = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
}
+ }
+
+ if (permissionResult === RESULTS.GRANTED || Platform.OS === 'ios') {
+ qrCodeRef.toDataURL(async (data) => {
+ try {
+ // 使用 ExternalDirectoryPath,这个目录在大多数 Android 设备上都可以访问
+ const basePath = Platform.OS === 'android'
+ ? `${RNFS.ExternalDirectoryPath}`
+ : RNFS.DocumentDirectoryPath;
+
+ const fileName = `qrcode_${Date.now()}.png`;
+ const filePath = `${basePath}/${fileName}`;
+
+ // 直接写入文件
+ await RNFS.writeFile(filePath, data, 'base64');
+
+ if (Platform.OS === 'android') {
+ // 使用 CameraRoll 保存到相册
+ const { CameraRoll } = NativeModules;
+ if (CameraRoll && CameraRoll.saveToCameraRoll) {
+ await CameraRoll.saveToCameraRoll(`file://${filePath}`, 'photo');
+ // 删除临时文件
+ await RNFS.unlink(filePath);
+ Alert.alert('成功', '二维码已保存到相册');
+ } else {
+ // 如果 CameraRoll 模块不可用,尝试使用 MediaScanner
+ NativeModules.MediaScanner.scanFile(filePath, 'image/png', (err) => {
+ if (err) {
+ console.error('媒体扫描错误:', err);
+ Alert.alert('提示', '二维码已保存,但可能需要手动刷新相册');
+ } else {
+ Alert.alert('成功', '二维码已保存到相册');
+ }
+ });
+ }
+ } else {
+ // iOS 处理
+ Alert.alert('成功', '二维码已保存');
+ }
+ } catch (err) {
+ console.error('文件操作错误:', err);
+ Alert.alert('错误', '保存失败,请检查存储权限');
+ }
+ });
} else {
- Alert.alert('权限不足', '请在设置中授予存储权限');
+ Alert.alert('提示', '需要存储权限才能保存二维码,请在设置中授予权限');
}
} catch (error) {
- console.error('整体错误:', error);
+ console.error('保存过程错误:', error);
Alert.alert('错误', '保存失败,请重试');
}
};
+
+ if (loading) {
+ return (
+
+
+ 加载中...
+
+ );
+ }
+
return (
-
-
+ setQrCodeRef(c)} // 保存二维码的引用
+ logo={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u3giTY4VkWYpnGWRuFHF' }} // 添加 logo
+ logoSize={rpx(100)} // 设置 logo 大小
+ logoBackgroundColor='transparent' // 设置 logo 背景透明
/>
-
+
@@ -105,14 +122,22 @@ const ShareQrcode = () => {
);
}
-
-// ... 其余代码保持不变 ...
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F3FCFF',
},
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ loadingText: {
+ marginTop: 10,
+ fontSize: 16,
+ color: '#888',
+ },
btn: {
marginTop: rpx(36),
width: rpx(614),
@@ -139,7 +164,6 @@ const styles = StyleSheet.create({
qrContainer: {
alignItems: 'center',
justifyContent: 'center',
- // marginTop: rpx(-50),
},
qrBack: {
marginTop: rpx(-20),
diff --git a/yarn.lock b/yarn.lock
index 0a18f93..34865d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7681,7 +7681,7 @@ prompts@^2.0.1, prompts@^2.3.2, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.0, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7718,7 +7718,7 @@ qrcode-terminal@0.11.0:
resolved "https://registry.npmmirror.com/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz"
integrity sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==
-qrcode@^1.5.4:
+qrcode@^1.5.1, qrcode@^1.5.4:
version "1.5.4"
resolved "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz"
integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==
@@ -7876,6 +7876,15 @@ react-native-qrcode-scanner@^1.5.5:
prop-types "^15.5.10"
react-native-permissions "^2.0.2"
+react-native-qrcode-svg@^6.3.12:
+ version "6.3.12"
+ resolved "https://registry.npmmirror.com/react-native-qrcode-svg/-/react-native-qrcode-svg-6.3.12.tgz"
+ integrity sha512-7Bx23ZdFNJJdVXyW9BJmFWdI5kccjnpotzmL3exkV0irUKTmj51jesxpn5sqtgVdYFE4IUVoGzdS+8qg6Ua9BA==
+ dependencies:
+ prop-types "^15.8.0"
+ qrcode "^1.5.1"
+ text-encoding "^0.7.0"
+
react-native-reanimated@^3.16.1:
version "3.16.3"
resolved "https://registry.npmmirror.com/react-native-reanimated/-/react-native-reanimated-3.16.3.tgz"
@@ -7911,7 +7920,7 @@ react-native-share@^11.0.4:
resolved "https://registry.npmmirror.com/react-native-share/-/react-native-share-11.1.0.tgz"
integrity sha512-kcpBR90d5//xc8H84HnX6YFeOk4A34mtHz4UEpb7Twbu049KafJwsp4KVVr/SrJwy8W0/Rbe880En9Hq0REamw==
-react-native-svg@*, react-native-svg@^15.10.1, react-native-svg@^9.4.0:
+react-native-svg@*, react-native-svg@^15.10.1, react-native-svg@^9.4.0, react-native-svg@>=13.2.0:
version "15.10.1"
resolved "https://registry.npmmirror.com/react-native-svg/-/react-native-svg-15.10.1.tgz"
integrity sha512-Hqz/doQciVFK/Df2v+wsW96oY5jxlta7rZ31KQYo78dlgvAHEaGr6paEOAMvlIruw7EHNQ0Vc1ZmJPJF2kfIPQ==
@@ -7939,7 +7948,7 @@ react-native-wheel-picker-android@^2.0.6:
dependencies:
moment "^2.22.0"
-react-native@*, "react-native@^0.0.0-0 || >=0.60 <1.0", "react-native@^0.0.0-0 || >=0.65 <1.0", react-native@^0.74.6, react-native@>=0.60.0, react-native@>=0.70.0:
+react-native@*, "react-native@^0.0.0-0 || >=0.60 <1.0", "react-native@^0.0.0-0 || >=0.65 <1.0", react-native@^0.74.6, react-native@>=0.60.0, react-native@>=0.63.4, react-native@>=0.70.0:
version "0.74.6"
resolved "https://registry.npmmirror.com/react-native/-/react-native-0.74.6.tgz"
integrity sha512-TZ8uLf+dH+nO5nFwjhMd4PqtraeNT5cXQ0ySAhq7qqbTBgalxO3UklsLFW3cTSedC+eLw6J3P3H62e3/MjpWNw==
@@ -9009,6 +9018,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
+text-encoding@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.npmmirror.com/text-encoding/-/text-encoding-0.7.0.tgz"
+ integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==
+
text-segmentation@^1.0.3:
version "1.0.3"
resolved "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz"