feat: 长按下载图片

This commit is contained in:
jinyaqiu 2025-07-22 17:16:21 +08:00
parent 106700618d
commit 857a22dd6a
4 changed files with 159 additions and 4 deletions

View File

@ -3,8 +3,8 @@ import LogoSvg from "@/assets/icons/svg/logo.svg";
import UserinfoTotalSvg from "@/assets/icons/svg/userinfoTotal.svg";
import { useTranslation } from 'react-i18next';
import { Linking, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import QRDownloadScreen from "./qrCode";
interface AppDownloadProps {
IOS_APP_STORE_URL: string,
@ -39,7 +39,8 @@ export const AppDownload = (props: AppDownloadProps) => {
<UserinfoTotalSvg style={{ marginBottom: -20, zIndex: 1 }} />
<HandlersSvg style={{ marginBottom: -4, zIndex: 3 }} />
<View style={styles.qrCode}>
<QRCode
<QRDownloadScreen url={platform == "ios" ? IOS_APP_STORE_URL : ANDROID_APK_URL} />
{/* <QRCode
value={platform == "ios" ? IOS_APP_STORE_URL : ANDROID_APK_URL}
size={200}
color="black"
@ -47,7 +48,7 @@ export const AppDownload = (props: AppDownloadProps) => {
logoSize={50}
logoBorderRadius={10}
logoBackgroundColor="#f0f0f0"
/>
/> */}
</View>
</View>
{/* Description */}

View File

@ -0,0 +1,90 @@
import * as Haptics from 'expo-haptics';
import * as MediaLibrary from 'expo-media-library';
import React, { useRef } from 'react';
import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { captureRef } from 'react-native-view-shot';
export default function QRDownloadScreen(prop: { url: string }) {
const qrViewRef = useRef<View>(null); // 用于截图的引用
const [qrValue] = React.useState(prop.url); // 二维码内容
const saveQRToGallery = async () => {
try {
// 触发轻震,提升交互感
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
// 请求相册写入权限
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
Alert.alert('权限被拒绝', '需要保存图片到相册的权限');
return;
}
if (!qrViewRef.current) return;
// 截取二维码视图
const uri = await captureRef(qrViewRef, {
format: 'png',
quality: 1,
result: 'tmpfile', // 返回临时文件路径
});
// 保存到相册
await MediaLibrary.saveToLibraryAsync(uri);
Alert.alert('✅ 成功', '二维码已保存到相册!');
} catch (error) {
console.error('保存失败:', error);
Alert.alert('❌ 失败', '无法保存图片,请重试');
}
};
return (
<View style={styles.container}>
<Text style={styles.title}></Text>
{/* 可截图的容器 */}
<TouchableOpacity onLongPress={saveQRToGallery} activeOpacity={0.8}>
<View ref={qrViewRef} style={styles.qrContainer}>
<QRCode value={qrValue} size={200} />
</View>
</TouchableOpacity>
<Text style={styles.tip}>👉 </Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f8f8f8',
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
qrContainer: {
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#ddd',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
tip: {
marginTop: 20,
color: '#666',
fontSize: 14,
},
});

63
package-lock.json generated
View File

@ -69,6 +69,7 @@
"react-native-svg": "^15.11.2",
"react-native-toast-message": "^2.3.0",
"react-native-uuid": "^2.0.3",
"react-native-view-shot": "4.0.3",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5",
"react-redux": "^9.2.0"
@ -6150,6 +6151,15 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@ -7066,6 +7076,15 @@
"hyphenate-style-name": "^1.0.3"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
@ -10002,6 +10021,19 @@
"void-elements": "3.1.0"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/htmlparser2": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
@ -15166,6 +15198,19 @@
"npm": ">=6.0.0"
}
},
"node_modules/react-native-view-shot": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz",
"integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==",
"license": "MIT",
"dependencies": {
"html2canvas": "^1.4.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-web": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz",
@ -17009,6 +17054,15 @@
"deprecated": "no longer maintained",
"license": "(Unlicense OR Apache-2.0)"
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -17683,6 +17737,15 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",

View File

@ -77,7 +77,8 @@
"react-native-uuid": "^2.0.3",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5",
"react-redux": "^9.2.0"
"react-redux": "^9.2.0",
"react-native-view-shot": "4.0.3"
},
"devDependencies": {
"@babel/core": "^7.25.2",