基于React Native 搭建 HarmonyOS-GitCode口袋工具-2
·
参考文章
【开源鸿蒙跨平台开发学习笔记】Day04:React Native 开发 HarmonyOS-GitCode口袋工具开发-2-CSDN博客
开源鸿蒙-基于React搭建GitCode口袋工具-2-CSDN博客
本文记录操作流程以及遇到的问题
一、GitCode的API认证
在api文件夹内,我们创建一个client.ts文件用来封装网络请求:
import axios from 'axios';
/**
* 统一的 Axios 客户端:
* - 基地址指向 GitCode v5 API
* - 请求拦截器自动注入 `private-token` 头,便于后续请求复用
* - 响应拦截器透传数据/错误,错误统一由 `getErrorMessage` 格式化
* - 提供 `setPrivateToken` 在运行时动态更新私有令牌
*/
// 默认令牌(仅用于开发调试),正式环境建议改为安全的配置或环境变量注入
const DEFAULT_TOKEN = '令牌';
let privateToken = DEFAULT_TOKEN;
export function setPrivateToken(token?: string) {
if (token) {
privateToken = token;
}
}
export const http = axios.create({
baseURL: 'https://api.gitcode.com/api/v5/',
timeout: 10000,
});
// 在每次请求前注入统一的头信息
http.interceptors.request.use(config => {
const headers = config.headers ?? {};
headers['private-token'] = privateToken;
config.headers = headers;
return config;
});
// 透传响应;错误在调用处统一处理
http.interceptors.response.use(
res => res,
err => Promise.reject(err),
);
// 将错误对象转换为用户可读的文案
export function getErrorMessage(error: unknown): string {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const data = error.response?.data as any;
const msg =
typeof data === 'string'
? data
: data?.message || data?.error || error.message;
return status ? `${status} ${msg}` : msg;
}
const e = error as any;
return String(e?.message || e);
}
1.1 更改user.tsx文件
在AwesomeProject\src\api,更改user.tsx文件
原文件内容:
import axios from 'axios';
import {UserProfile} from '../types/user';
const BASE_URL = 'https://api.gitcode.com/api/v5/users';
export async function fetchUserProfile(username: string): Promise<UserProfile> {
const res = await axios.get<UserProfile>(`${BASE_URL}/${encodeURIComponent(username)}`, {timeout: 10000});
return res.data;
}
更改后:
import {http} from './client';
import {UserProfile} from '../types/user';
const PROFILE_PATH = 'users/weinxi_74220422';
export async function fetchUserProfile(username: string): Promise<UserProfile> {
const res = await http.get<UserProfile>(PROFILE_PATH);
return res.data;
}
export async function fetchStarred(username: string): Promise<any[]> {
const res = await http.get<any[]>(`users/${username}/starred`);
return res.data;
}
1.2 更改HomeScreen.tsx或者ExploreScreen.tsx文件
在AwesomeProject\src\screens目录下,更改文件
看你喜欢放在哪个页面就改哪个页面的代码,我这里把原文件都粘下来,方便换
我这里选择把修改的代码放到ExploreScreen.tsx文件,如果放到HomeScreen.tsx要记得更改引用的路径
HomeScreen.tsx原文件:
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
interface HomeScreenProps {}
export function HomeScreen(_: HomeScreenProps) {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello World</Text>
<Text style={styles.subTitle}>React Native + Harmony</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {alignItems: 'center', justifyContent: 'center'},
title: {fontSize: 22, fontWeight: '700', color: '#111827'},
subTitle: {marginTop: 8, fontSize: 16, color: '#374151'},
});
ExploreScreen.tsx原文件:
import React, {useEffect, useState} from 'react';
import {View, Text, StyleSheet, Image, ActivityIndicator, ScrollView, TouchableOpacity, Linking} from 'react-native';
import {fetchUserProfile} from '../api';
import {UserProfile} from '../types/user';
interface ExploreScreenProps {
username?: string;
}
export function ExploreScreen({username = 'weinxi_74220422'}: ExploreScreenProps) {
const [data, setData] = useState<UserProfile | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasError, setHasError] = useState<string>('');
useEffect(function load() {
let mounted = true;
setIsLoading(true);
setHasError('');
fetchUserProfile(username)
.then(d => {
if (!mounted) return;
setData(d);
})
.catch(e => {
if (!mounted) return;
setHasError(String(e?.message || e));
})
.finally(() => {
if (!mounted) return;
setIsLoading(false);
});
return function cleanup() {
mounted = false;
};
}, [username]);
if (isLoading) {
return (
<View style={styles.center}>
<ActivityIndicator />
<Text style={styles.loadingText}>加载中</Text>
</View>
);
}
if (hasError) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>请求失败:{hasError}</Text>
</View>
);
}
if (!data) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>暂无数据</Text>
</View>
);
}
return (
<ScrollView contentContainerStyle={styles.scrollContent} style={styles.scroll}>
<Image source={{uri: data.avatar_url}} style={styles.avatar} />
<Text style={styles.title}>{data.name || data.login}</Text>
<Text style={styles.subtitle}>类型:{data.type}</Text>
<Text style={styles.subtitle}>粉丝:{data.followers},关注:{data.following}</Text>
{Boolean(data.bio) && <Text style={styles.bio}>{data.bio}</Text>}
<TouchableOpacity onPress={() => Linking.openURL(String(data.html_url))} style={styles.linkButton} activeOpacity={0.9}>
<Text style={styles.linkText}>打开主页</Text>
</TouchableOpacity>
</ScrollView>
);
}
const styles = StyleSheet.create({
center: {flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#FFFFFF'},
loadingText: {marginTop: 8, fontSize: 14, color: '#666'},
errorText: {fontSize: 14, color: '#d00'},
scroll: {flex: 1, backgroundColor: '#FFFFFF'},
scrollContent: {alignItems: 'center', paddingVertical: 24},
avatar: {width: 120, height: 120, borderRadius: 60, backgroundColor: '#eee'},
title: {marginTop: 16, fontSize: 24, fontWeight: '700'},
subtitle: {marginTop: 8, fontSize: 16, color: '#666'},
bio: {marginTop: 12, fontSize: 14, color: '#333', paddingHorizontal: 24, textAlign: 'center'},
linkButton: {marginTop: 16, paddingHorizontal: 16, paddingVertical: 10, borderRadius: 6, backgroundColor: '#007aff'},
linkText: {color: '#fff', fontSize: 14, fontWeight: '600'},
});
修改代码:
import React, {useEffect, useState} from 'react';
import {
View,
Text,
StyleSheet,
Image,
ActivityIndicator,
ScrollView,
Pressable,
Linking,
} from 'react-native';
import {fetchUserProfile, fetchStarred} from '../api';
import {UserProfile} from '../types/user';
import {getErrorMessage} from '../api/client';
// 已经是 ExploreScreen 和默认导出
export function ExploreScreen(): JSX.Element {
const [data, setData] = useState<UserProfile | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [starred, setStarred] = useState<any[]>([]);
useEffect(() => {
let mounted = true;
setLoading(true);
setError('');
Promise.all([fetchUserProfile('Deng666'), fetchStarred('Deng666')])
.then(([d, s]) => {
if (!mounted) {
return;
}
setData(d);
setStarred(Array.isArray(s) ? s : []);
})
.catch(e => {
if (!mounted) {
return;
}
setError(getErrorMessage(e));
})
.finally(() => {
if (!mounted) {
return;
}
setLoading(false);
});
return () => {
mounted = false;
};
}, []);
// 安全的打开链接函数
const safeOpenURL = (url: string | undefined) => {
if (url && typeof url === 'string' && url.trim() !== '') {
Linking.openURL(url);
} else {
console.warn('无效的 URL:', url);
}
};
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator />
<Text style={styles.loadingText}>加载中</Text>
</View>
);
}
if (error) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>请求失败:{error}</Text>
</View>
);
}
if (!data) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>暂无数据</Text>
</View>
);
}
return (
<ScrollView contentContainerStyle={styles.scrollContent}>
<Image source={{uri: data.avatar_url}} style={styles.avatar} />
<Text style={styles.title}>{data.name || data.login}</Text>
<Text style={styles.subtitle}>类型:{data.type}</Text>
<Text style={styles.subtitle}>
粉丝:{data.followers},关注:{data.following}
</Text>
{Boolean(data.bio) && <Text style={styles.bio}>{data.bio}</Text>}
<Pressable
onPress={() => safeOpenURL(data.html_url)}
style={styles.linkButton}>
<Text style={styles.linkText}>打开主页</Text>
</Pressable>
<View style={styles.listHeader}>
<Text style={styles.listHeaderText}>已 Star 的仓库</Text>
</View>
{starred.map((item, idx) => {
const name =
item?.name || item?.path || item?.project_name || '未知仓库';
const desc = item?.description || '';
const link = item?.html_url || item?.web_url || item?.url || '';
return (
<View key={`${name}-${idx}`} style={styles.repoCard}>
<Text style={styles.repoName}>{name}</Text>
{!!desc && <Text style={styles.repoDesc}>{desc}</Text>}
{!!link && (
<Pressable
onPress={() => safeOpenURL(link)}
style={styles.repoLinkBtn}>
<Text style={styles.repoLinkText}>访问仓库</Text>
</Pressable>
)}
</View>
);
})}
</ScrollView>
);
}
const styles = StyleSheet.create({
center: {flex: 1, alignItems: 'center', justifyContent: 'center'},
loadingText: {marginTop: 8, fontSize: 14, color: '#666'},
errorText: {fontSize: 14, color: '#d00'},
scrollContent: {alignItems: 'center', paddingVertical: 24},
avatar: {width: 120, height: 120, borderRadius: 60, backgroundColor: '#eee'},
title: {marginTop: 16, fontSize: 24, fontWeight: '700'},
subtitle: {marginTop: 8, fontSize: 16, color: '#666'},
bio: {
marginTop: 12,
fontSize: 14,
color: '#333',
paddingHorizontal: 24,
textAlign: 'center',
},
linkButton: {
marginTop: 16,
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 6,
backgroundColor: '#007aff',
},
linkText: {color: '#fff', fontSize: 14, fontWeight: '600'},
listHeader: {width: '100%', paddingHorizontal: 24, paddingTop: 24},
listHeaderText: {fontSize: 18, fontWeight: '600'},
repoCard: {
width: '92%',
marginTop: 12,
padding: 12,
borderRadius: 8,
backgroundColor: '#f7f7f7',
},
repoName: {fontSize: 16, fontWeight: '600'},
repoDesc: {marginTop: 6, fontSize: 14, color: '#555'},
repoLinkBtn: {
marginTop: 10,
alignSelf: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 6,
backgroundColor: '#34c759',
},
repoLinkText: {color: '#fff', fontSize: 14, fontWeight: '600'},
});
二、编译
执行编译的命令npm run dev,将生成的"\AwesomeProject\harmony\entry\src\main\resources\rawfile"目录下的"bundle.harmony.js"拷贝到鸿蒙项目里的"rawfile"目录下
出现报错
HomeScreen.tsx文件

原因可能是用户名缺失或者传少了一个username参数
解决方法:
修改user.tsx文件的代码,顺便看看HomeScreen.tsx文件的用户参数有没有写漏
我这里就是user.tsx文件传少了一个参数,HomeScreen.tsx文件少了引用了一个用户参数
import {http} from './client';
import {UserProfile} from '../types/user';
const PROFILE_PATH = 'users/weinxi_74220422';
export async function fetchUserProfile(username: string): Promise<UserProfile> {
const res = await http.get<UserProfile>(PROFILE_PATH);
return res.data;
}
export async function fetchStarred(username: string): Promise<any[]> {
const res = await http.get<any[]>(`users/${username}/starred`);
return res.data;
}
HomeScreen.tsx文件:

解决方法:
使用非空断言
<Pressable
onPress={() => Linking.openURL(data.html_url!)}
style={styles.linkButton}>
<Text style={styles.linkText}>打开主页</Text>
</Pressable>
三、成功完成

更多推荐

所有评论(0)