Browse Source

谷歌登录获取用户信息

main
Mac 2 weeks ago
parent
commit
1f07011d20
  1. 5
      app/screens/address/AddAddress.tsx
  2. 90
      app/screens/address/EditAddress.tsx
  3. 285
      app/screens/loginList/index.tsx
  4. 11
      app/services/api/login.ts

5
app/screens/address/AddAddress.tsx

@ -29,6 +29,7 @@ import { settingApi } from "../../services/api/setting";
import flagMap from "../../utils/flagMap";
import { useTranslation } from "react-i18next";
import fontSize from "../../utils/fontsizeUtils";
import { getCountryTransLanguage } from "../../utils/languageUtils";
type RootStackParamList = {
@ -109,10 +110,10 @@ export const AddAddress = () => {
useEffect(() => {
settingApi.getCountryList().then((res) => {
const formattedCountries = res.map((item) => ({
label: `${item.name_en} (${item.country})`,
label: `${getCountryTransLanguage(item)} (${item.country})`,
value: item.name.toString(),
flag: flagMap.get(item.name_en),
name_en: item.name_en,
name_en: getCountryTransLanguage(item),
country: item.country
}));
setItems(formattedCountries);

90
app/screens/address/EditAddress.tsx

@ -28,6 +28,7 @@ import { settingApi } from "../../services/api/setting";
import flagMap from "../../utils/flagMap";
import { useTranslation } from "react-i18next";
import fontSize from "../../utils/fontsizeUtils";
import { getCountryTransLanguage } from "../../utils/languageUtils";
type RootStackParamList = {
AddRess: { address?: AddressItem };
@ -53,7 +54,7 @@ export const EditAddress = () => {
const [open, setOpen] = useState(false);
const [value, setValue] = useState<string | null>(null);
const [items, setItems] = useState<{label: string; value: string}[]>([]);
const [items, setItems] = useState<{ label: string; value: string }[]>([]);
const [selectedCountryLabel, setSelectedCountryLabel] = useState<string>("");
const [countryList, setCountryList] = useState<any[]>([]);
@ -91,11 +92,11 @@ export const EditAddress = () => {
useEffect(() => {
settingApi.getCountryList().then((res) => {
const formattedCountries = res.map((item) => ({
label: `${item.name_en} (${item.country})`,
label: `${getCountryTransLanguage(item)} (${item.country})`,
value: item.name.toString(),
flag: flagMap.get(item.name_en),
name_en: item.name_en,
country: item.country
name_en: getCountryTransLanguage(item),
country: item.country,
}));
setItems(formattedCountries);
setCountryList(formattedCountries);
@ -108,7 +109,8 @@ export const EditAddress = () => {
// 如果有路由参数中的地址
const addressFromRoute = route.params?.address;
if (addressFromRoute && addressFromRoute.country) {
const selectedCountry = countryList.find(item =>
const selectedCountry = countryList.find(
(item) =>
item.name_en === addressFromRoute.country ||
item.value === addressFromRoute.country
);
@ -155,7 +157,9 @@ export const EditAddress = () => {
newErrors.receiver_phone = t("address.errors.phone_required");
}
if (!formData.receiver_phone_again) {
newErrors.receiver_phone_again = t("address.errors.confirm_phone_required");
newErrors.receiver_phone_again = t(
"address.errors.confirm_phone_required"
);
}
if (formData.receiver_phone !== formData.receiver_phone_again) {
newErrors.receiver_phone_again = t("address.errors.phone_mismatch");
@ -170,7 +174,9 @@ export const EditAddress = () => {
const handleSubmit = async () => {
if (validateForm()) {
const selectedCountryObj = countryList.find(item => item.value === value);
const selectedCountryObj = countryList.find(
(item) => item.value === value
);
const submitData = {
...formData,
country: selectedCountryObj ? selectedCountryObj.name_en : "",
@ -219,14 +225,18 @@ export const EditAddress = () => {
</TouchableOpacity>
</View>
<Text style={styles.titleHeading}>{t("address.select_recipient")}</Text>
<Text style={styles.titleHeading}>
{t("address.select_recipient")}
</Text>
</View>
<View style={styles.recipientInfoForm}>
<View style={styles.contactFormContainer}>
{/* First Name Field */}
<View style={styles.formFieldContainer}>
<View style={styles.flexRowCentered}>
<Text style={styles.elegantTextSnippet}>{t("address.first_name")}</Text>
<Text style={styles.elegantTextSnippet}>
{t("address.first_name")}
</Text>
<Text style={styles.redTextHeading}>*</Text>
</View>
<TextInput
@ -250,7 +260,9 @@ export const EditAddress = () => {
{/* Last Name Field */}
<View style={styles.lastNameInputContainer}>
<View style={styles.flexRowCentered}>
<Text style={styles.elegantTextSnippet}>{t("address.last_name")}</Text>
<Text style={styles.elegantTextSnippet}>
{t("address.last_name")}
</Text>
<Text style={styles.redAsteriskTextStyle}>*</Text>
</View>
<TextInput
@ -258,7 +270,10 @@ export const EditAddress = () => {
placeholder={t("address.placeholder.last_name")}
value={formData.receiver_last_name}
onChangeText={(text) =>
setFormData({ ...formData, receiver_last_name: text })
setFormData({
...formData,
receiver_last_name: text,
})
}
/>
{errors.receiver_last_name && (
@ -270,16 +285,22 @@ export const EditAddress = () => {
{/* 国家 */}
<View style={styles.lastNameInputContainer}>
<View style={styles.flexRowCentered}>
<Text style={styles.elegantTextSnippet}>{t("address.country")}</Text>
<Text style={styles.elegantTextSnippet}>
{t("address.country")}
</Text>
</View>
<TouchableOpacity
style={styles.countrySelectorButton}
onPress={() => setOpen(true)}
>
{selectedCountryLabel ? (
<Text style={styles.selectedCountryText}>{selectedCountryLabel}</Text>
<Text style={styles.selectedCountryText}>
{selectedCountryLabel}
</Text>
) : (
<Text style={styles.placeholderStyle}>{t("address.placeholder.select_country")}</Text>
<Text style={styles.placeholderStyle}>
{t("address.placeholder.select_country")}
</Text>
)}
<Text style={styles.dropdownArrow}></Text>
</TouchableOpacity>
@ -293,11 +314,15 @@ export const EditAddress = () => {
<Text style={styles.elegantTextSnippet}>
{t("address.phone_number")}
</Text>
<Text style={styles.redAsteriskTextStyle}>*</Text>
<Text style={styles.redAsteriskTextStyle}>
*
</Text>
</View>
<TextInput
style={styles.pingFangText1}
placeholder={t("address.placeholder.phone_number")}
placeholder={t(
"address.placeholder.phone_number"
)}
value={formData.receiver_phone}
onChangeText={(text) =>
setFormData({
@ -317,11 +342,15 @@ export const EditAddress = () => {
<Text style={styles.elegantTextSnippet}>
{t("address.confirm_phone_number")}
</Text>
<Text style={styles.redAsteriskTextStyle}>*</Text>
<Text style={styles.redAsteriskTextStyle}>
*
</Text>
</View>
<TextInput
style={styles.pingFangText1}
placeholder={t("address.placeholder.confirm_phone_number")}
placeholder={t(
"address.placeholder.confirm_phone_number"
)}
value={formData.receiver_phone_again}
onChangeText={(text) =>
setFormData({
@ -347,7 +376,9 @@ export const EditAddress = () => {
{/* WhatsApp Field */}
<View style={styles.lastNameInputContainer}>
<View style={styles.flexRowCentered}>
<Text style={styles.elegantTextSnippet}>{t("address.whatsapp")}</Text>
<Text style={styles.elegantTextSnippet}>
{t("address.whatsapp")}
</Text>
<Text style={styles.redTextHeading}>*</Text>
</View>
<TextInput
@ -401,7 +432,9 @@ export const EditAddress = () => {
})
}
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={formData.is_default ? "#002fa7" : "#f4f3f4"}
thumbColor={
formData.is_default ? "#002fa7" : "#f4f3f4"
}
ios_backgroundColor="#3e3e3e"
/>
</View>
@ -414,7 +447,9 @@ export const EditAddress = () => {
style={styles.primaryButtonStyle}
onPress={handleSubmit}
>
<Text style={styles.buttonText}>{t("address.submit")}</Text>
<Text style={styles.buttonText}>
{t("address.submit")}
</Text>
</TouchableOpacity>
</View>
</View>
@ -431,7 +466,9 @@ export const EditAddress = () => {
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>{t("address.select_country")}</Text>
<Text style={styles.modalTitle}>
{t("address.select_country")}
</Text>
<TouchableOpacity onPress={() => setOpen(false)}>
<Text style={styles.closeButton}>{t("address.close")}</Text>
</TouchableOpacity>
@ -445,10 +482,7 @@ export const EditAddress = () => {
onPress={() => handleCountrySelect(item)}
>
{item.flag && (
<Image
source={item.flag}
style={styles.flagImage}
/>
<Image source={item.flag} style={styles.flagImage} />
)}
<Text style={styles.countryItemText}>{item.label}</Text>
{value === item.value && (
@ -471,7 +505,7 @@ export const EditAddress = () => {
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: "#fff",
},
safeAreaContent: {
flex: 1,
@ -854,7 +888,7 @@ const styles = StyleSheet.create({
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
width: "100%",
height: Dimensions.get('window').height * 0.8,
height: Dimensions.get("window").height * 0.8,
display: "flex",
flexDirection: "column",
},

285
app/screens/loginList/index.tsx

@ -10,7 +10,7 @@ import {
Image,
Modal,
SafeAreaView,
Alert
Alert,
} from "react-native";
import { useTranslation } from "react-i18next";
import { useNavigation } from "@react-navigation/native";
@ -21,31 +21,32 @@ import PhoneLoginModal from "./PhoneLoginModal";
import { loginApi } from "../../services/api/login";
import { userApi } from "../../services";
import useUserStore from "../../store/user";
import AsyncStorage from "@react-native-async-storage/async-storage";
// 使用标准的ES6模块导入
// import {
// GoogleSignin,
// statusCodes,
// } from "@react-native-google-signin/google-signin";
import {
GoogleSignin,
statusCodes,
} from "@react-native-google-signin/google-signin";
// import { LoginManager, AccessToken, Settings } from "react-native-fbsdk-next";
import { LoginManager, AccessToken, Settings } from "react-native-fbsdk-next";
const isDevelopment = __DEV__; // 开发模式检测
// 移出条件块,始终尝试配置 Google 登录
// try {
// // 配置 Google 登录
// GoogleSignin.configure({
// iosClientId: "YOUR_IOS_CLIENT_ID_HERE.apps.googleusercontent.com", // iOS CLIENT_ID
// webClientId:
// "449517618313-av37nffa7rqkefu0ajh5auou3pb0mt51.apps.googleusercontent.com", // <-- 更新为此 Web Client ID
// scopes: ["profile", "email"],
// offlineAccess: false, // <-- 确保为 false 或移除
// forceCodeForRefreshToken: false, // <-- 确保为 false 或移除
// });
// } catch (error) {
// console.log("Google Sign-in模块配置错误:", error); // 稍微修改了日志信息
// }
try {
// 配置 Google 登录
GoogleSignin.configure({
iosClientId: "YOUR_IOS_CLIENT_ID_HERE.apps.googleusercontent.com", // iOS CLIENT_ID
webClientId:
"449517618313-av37nffa7rqkefu0ajh5auou3pb0mt51.apps.googleusercontent.com", // <-- 更新为此 Web Client ID
scopes: ["profile", "email"],
offlineAccess: false, // <-- 确保为 false 或移除
forceCodeForRefreshToken: false, // <-- 确保为 false 或移除
});
} catch (error) {
console.log("Google Sign-in模块配置错误:", error); // 稍微修改了日志信息
}
type RootStackParamList = {
Login: undefined;
@ -108,122 +109,154 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
// 处理谷歌登录
const handleGoogleLogin = async () => {
// try {
// if (!GoogleSignin || typeof GoogleSignin.signIn !== "function") {
// console.log("Google Sign-in模块未正确初始化或配置失败");
// return;
// }
// await GoogleSignin.hasPlayServices();
// const userInfo = await GoogleSignin.signIn();
// console.log("Google 登录成功:", userInfo);
// try {
// const res = await loginApi.googleLogin(userInfo);
// const user = await userApi.getProfile();
// setUser(user);
try {
if (!GoogleSignin || typeof GoogleSignin.signIn !== "function") {
console.log("Google Sign-in模块未正确初始化或配置失败");
return;
}
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
console.log("Google 登录成功:", userInfo);
try {
const res = await loginApi.googleLogin(userInfo);
const token = res.token_type + " " + res.access_token;
await AsyncStorage.setItem("token", token);
const user = await userApi.getProfile();
setUser(user);
navigation.navigate("MainTabs", { screen: "Home" });
} catch (err) {
console.log("Google 登录失败:", err);
navigation.navigate("Login");
}
// 这里可以处理登录成功后的逻辑
// 比如导航到主页面或保存用户信息
// navigation.navigate("MainTabs", { screen: "Home" });
// } catch (err) {
// console.log("Google 登录失败:", err);
// navigation.navigate("Login");
// }
// // 这里可以处理登录成功后的逻辑
// // 比如导航到主页面或保存用户信息
// // navigation.navigate("MainTabs", { screen: "Home" });
// } catch (error: any) {
// console.log("Google 登录错误:", error);
// // 开发模式下的错误处理
// if (isDevelopment) {
// console.log("开发模式:忽略Google登录错误,但已尝试真实登录"); // 修改日志,表明已尝试真实登录
// return;
// }
// if (statusCodes && error.code === statusCodes.SIGN_IN_CANCELLED) {
// console.log("用户取消登录");
// } else if (statusCodes && error.code === statusCodes.IN_PROGRESS) {
// console.log("登录正在进行中");
// } else if (
// statusCodes &&
// error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE
// ) {
// console.log("Play Services 不可用");
// } else {
// console.log("其他错误:", error.message);
// navigation.navigate("Login");
// }
// }
} catch (error: any) {
console.log("Google 登录错误:", error);
// 开发模式下的错误处理
if (isDevelopment) {
console.log("开发模式:忽略Google登录错误,但已尝试真实登录"); // 修改日志,表明已尝试真实登录
return;
}
if (statusCodes && error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log("用户取消登录");
} else if (statusCodes && error.code === statusCodes.IN_PROGRESS) {
console.log("登录正在进行中");
} else if (
statusCodes &&
error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE
) {
console.log("Play Services 不可用");
} else {
console.log("其他错误:", error.message);
navigation.navigate("Login");
}
}
};
const [userInfo, setUserInfo] = useState<any>(null);
// useEffect(() => {
// // 确保在 App 启动时初始化 SDK。这通常在您的 App.js 的顶层完成。
// // 如果您在 app.json 中配置了 Facebook App ID,这里可以省略 Settings.setAppID 和 Settings.setDisplayName
// Settings.initializeSDK();
// // 在应用程序启动时检查是否已经登录(可选)
// AccessToken.getCurrentAccessToken().then(data => {
// if (data) {
// console.log("已登录 Facebook,Token:", data.accessToken);
// // 可以尝试获取用户信息
// // fetchFacebookProfile(data.accessToken);
// }
// });
// }, []);
useEffect(() => {
// 确保在 App 启动时初始化 SDK。这通常在您的 App.js 的顶层完成。
// 如果您在 app.json 中配置了 Facebook App ID,这里可以省略 Settings.setAppID 和 Settings.setDisplayName
Settings.initializeSDK();
// 在应用程序启动时检查是否已经登录(可选)
AccessToken.getCurrentAccessToken().then((data) => {
if (data) {
console.log("已登录 Facebook,Token:", data.accessToken);
// 可以尝试获取用户信息
// fetchFacebookProfile(data.accessToken);
}
});
}, []);
// 辅助函数:获取 Facebook 用户资料 (可选,需要 'public_profile' 权限)
// const fetchFacebookProfile = async (token:string) => {
// try {
// const response = await fetch(`https://graph.facebook.com/me?fields=id,name,email&access_token=${token}`);
// const profile = await response.json();
// setUserInfo(profile);
// console.log('Facebook User Info:', profile);
// } catch (error) {
// console.error('获取 Facebook 用户资料错误:', error);
// Alert.alert("获取资料失败", "无法从 Facebook 获取用户详细资料,请检查网络或权限设置。");
// }
// };
const fetchFacebookProfile = async (token: string) => {
try {
const response = await fetch(
`https://graph.facebook.com/me?fields=id,name,email&access_token=${token}`
);
const profile = await response.json();
setUserInfo(profile);
console.log("Facebook User Info:", profile);
return profile;
} catch (error) {
console.error("获取 Facebook 用户资料错误:", error);
Alert.alert(
"获取资料失败",
"无法从 Facebook 获取用户详细资料,请检查网络或权限设置。"
);
return null;
}
};
// 处理Facebook登录
const handleFacebookLogin = async () => {
// try {
// // 可选: 先退出登录,确保每次都是全新登录 (主要用于测试)
// // await LoginManager.logOut();
// const result = await LoginManager.logInWithPermissions([
// "public_profile",
// "email",
// ]);
// if (result.isCancelled) {
// Alert.alert("登录取消", "用户取消了 Facebook 登录。");
// return;
// }
// const data = await AccessToken.getCurrentAccessToken();
// // 确保 accessToken 存在且为字符串
// if (!data || !data.accessToken) {
// Alert.alert("登录失败", "无法获取有效的 Facebook AccessToken。");
// return;
// }
// const tokenString = data.accessToken.toString();
// console.log("Facebook Access Token:", tokenString);
// // 直接获取 Facebook 用户信息并打印
// await fetchFacebookProfile(tokenString);
// // 移除之前的 Alert, 因为 fetchFacebookProfile 内部会处理打印和可能的错误提示
// // 如果 fetchFacebookProfile 成功,信息已在控制台,如果失败,它会 Alert
// // 可以选择在这里加一个通用成功提示,表明流程已执行
// Alert.alert("操作完成", "已尝试 Facebook 登录并获取用户信息,请查看控制台输出。");
// } catch (error: any) {
// console.error("Facebook 登录或获取资料时发生错误:", error);
// let errorMessage = "发生未知错误";
// if (error && typeof error.message === 'string') {
// errorMessage = error.message;
// }
// Alert.alert("登录错误", `Facebook 操作失败:${errorMessage}`);
// }
try {
// 可选: 先退出登录,确保每次都是全新登录 (主要用于测试)
// await LoginManager.logOut();
const result = await LoginManager.logInWithPermissions([
"public_profile",
"email",
]);
if (result.isCancelled) {
Alert.alert("登录取消", "用户取消了 Facebook 登录。");
return;
}
const data = await AccessToken.getCurrentAccessToken();
// 确保 accessToken 存在且为字符串
if (!data || !data.accessToken) {
Alert.alert("登录失败", "无法获取有效的 Facebook AccessToken。");
return;
}
const tokenString = data.accessToken.toString();
console.log("Facebook Access Token:", tokenString);
// 获取 Facebook 用户信息
const profile = await fetchFacebookProfile(tokenString);
if (profile) {
try {
// 准备发送给后端的数据 - 扁平化格式
const facebookData = {
id: profile.id,
name: profile.name,
email: profile.email,
access_token: tokenString,
};
console.log("发送给后端的Facebook数据:", facebookData);
// 调用后端Facebook登录API
const res = await loginApi.facebookLogin(facebookData);
console.log("Facebook 后端登录响应:", res);
const token = res.token_type + " " + res.access_token;
await AsyncStorage.setItem("token", token);
// 获取用户信息并更新状态
const user = await userApi.getProfile();
setUser(user);
// 导航到主页面
navigation.navigate("MainTabs", { screen: "Home" });
} catch (err) {
console.log("Facebook 后端登录失败:", err);
Alert.alert("登录失败", "Facebook 登录验证失败,请重试。");
}
} else {
Alert.alert("登录失败", "无法获取 Facebook 用户信息,请重试。");
}
} catch (error: any) {
console.error("Facebook 登录或获取资料时发生错误:", error);
let errorMessage = "发生未知错误";
if (error && typeof error.message === "string") {
errorMessage = error.message;
}
Alert.alert("登录错误", `Facebook 操作失败:${errorMessage}`);
}
};
// 处理Apple登录

11
app/services/api/login.ts

@ -1,15 +1,22 @@
import apiService from "./apiClient";
export interface LoginResponse {
access_token: string;
token_type: string;
expires_in: number;
refresh_token: string;
scope: string;
}
export const loginApi = {
// 谷歌登录
googleLogin: (data: any) =>
apiService.post("/api/users/auth/callback/google", data),
apiService.post<LoginResponse>("/api/users/auth/callback/google", data),
// 苹果登录
appleLogin: (data: any) =>
apiService.post("/api/users/auth/callback/apple", data),
// 脸书登录
facebookLogin: (data: any) =>
apiService.post("/api/users/auth/callback/facebook", data),
apiService.post<LoginResponse>("/api/users/auth/callback/facebook", data),
};

Loading…
Cancel
Save