GitCode 口袋工具开发指南(2)
可以访问上一篇文章查看。
前期准备与项目搭建
在开始项目开发前,请确保已完成以下准备工作:
-
搭建完整的Cordova开发环境
-
注册GitCode账户并获取访问令牌
-
熟悉GitCode OpenAPI的基本使用
详细的前期准备步骤可参考:https://blog.csdn.net/yanwydxf/article/details/155266874?spm=1001.2014.3001.5502
口袋工具页面构建
界面设计理念
我们的GitCode口袋工具采用现代化设计语言,以简洁、直观的用户体验为核心,确保用户在移动设备上也能获得良好的操作体验。
页面结构分析
启动 DevEco Studio 打开 cordova 创建的项目,在 index.html 编辑页面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>口袋工具 - GitCode搜索</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #4a6ee0;
--primary-light: #e8edff;
--secondary-color: #6c757d;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--light-color: #f8f9fa;
--dark-color: #343a40;
--border-radius: 8px;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: var(--dark-color);
line-height: 1.6;
min-height: 100vh;
}
header {
background: linear-gradient(135deg, var(--primary-color), #6a82fb);
color: white;
padding: 2rem 1rem;
text-align: center;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
margin-bottom: 2rem;
box-shadow: var(--box-shadow);
}
header h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
font-weight: 700;
}
header p {
font-size: 1.1rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto;
}
main {
max-width: 800px;
margin: 0 auto;
padding: 0 1.5rem;
}
.card {
background: white;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 2rem;
margin-bottom: 2rem;
transition: var(--transition);
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--dark-color);
}
input, select {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid #ddd;
border-radius: var(--border-radius);
font-size: 1rem;
transition: var(--transition);
}
input:focus, select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px var(--primary-light);
}
.btn {
display: inline-block;
background: var(--primary-color);
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: var(--border-radius);
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
text-align: center;
}
.btn:hover {
background: #3a5ed0;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(74, 110, 224, 0.3);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
background: var(--secondary-color);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-secondary {
background: var(--secondary-color);
}
.btn-secondary:hover {
background: #5a6268;
}
.button-group {
display: flex;
gap: 1rem;
}
#result {
min-height: 200px;
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--box-shadow);
margin-top: 2rem;
}
.result-placeholder {
text-align: center;
color: var(--secondary-color);
padding: 3rem 1rem;
}
.result-placeholder i {
font-size: 3rem;
margin-bottom: 1rem;
color: #ddd;
}
.loading {
display: none;
text-align: center;
padding: 2rem;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top: 4px solid var(--primary-color);
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.result-item {
border-bottom: 1px solid #eee;
padding: 1rem 0;
display: flex;
align-items: flex-start;
}
.result-item:last-child {
border-bottom: none;
}
.result-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 1rem;
flex-shrink: 0;
object-fit: cover;
}
.result-content {
flex: 1;
}
.result-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
display: flex;
align-items: center;
}
.result-title a {
color: inherit;
text-decoration: none;
}
.result-title a:hover {
text-decoration: underline;
}
.result-desc {
color: var(--secondary-color);
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.result-meta {
display: flex;
gap: 1rem;
font-size: 0.8rem;
color: var(--secondary-color);
flex-wrap: wrap;
}
.result-meta span {
display: flex;
align-items: center;
}
.result-meta i {
margin-right: 0.3rem;
}
.error-message {
background-color: #ffeaea;
color: var(--danger-color);
padding: 1rem;
border-radius: var(--border-radius);
margin-bottom: 1rem;
border-left: 4px solid var(--danger-color);
}
.success-message {
background-color: #e8f5e9;
color: var(--success-color);
padding: 1rem;
border-radius: var(--border-radius);
margin-bottom: 1rem;
border-left: 4px solid var(--success-color);
}
.no-results {
text-align: center;
padding: 2rem;
color: var(--secondary-color);
}
.result-count {
margin-bottom: 1rem;
padding: 0.5rem;
background: #f8f9fa;
border-radius: 4px;
font-weight: 600;
}
footer {
text-align: center;
padding: 2rem 1rem;
color: var(--secondary-color);
font-size: 0.9rem;
margin-top: 2rem;
}
.token-info {
font-size: 0.8rem;
color: var(--secondary-color);
margin-top: 0.5rem;
}
@media (min-width: 768px) {
.form-row {
display: flex;
gap: 1.5rem;
}
.form-group {
flex: 1;
}
.button-group {
justify-content: flex-start;
}
.btn {
width: auto;
min-width: 150px;
}
}
@media (max-width: 767px) {
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>
</head>
<body>
<header>
<h1><i class="fas fa-search"></i> 口袋工具</h1>
<p>基于GitCode API的智能搜索工具,快速找到您需要的用户和仓库</p>
</header>
<main>
<div class="card">
<div class="form-group">
<label for="key"><i class="fas fa-key"></i> 访问令牌</label>
<input type="text" id="key" placeholder="请输入您的GitCode个人访问令牌" value="" />
<div class="token-info">
您可以在GitCode的"设置" → "访问令牌"中创建个人访问令牌
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="category"><i class="fas fa-filter"></i> 选择类别</label>
<select id="category">
<option value="users">用户</option>
<option value="projects">仓库</option>
</select>
</div>
<div class="form-group">
<label for="keyword"><i class="fas fa-search"></i> 关键词</label>
<input type="text" id="keyword" placeholder="请输入搜索关键词" />
</div>
</div>
<div class="button-group">
<button id="btnquery" class="btn">
<i class="fas fa-search"></i> 检索
</button>
<button id="btnTest" class="btn btn-secondary">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
</div>
<div id="result">
<div class="result-placeholder">
<i class="fas fa-search"></i>
<p>请输入搜索条件并点击"检索"按钮查看结果</p>
</div>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在检索中,请稍候...</p>
</div>
</main>
<footer>
<p>© 2025 口袋工具 - 便捷检索GitCode信息 | 基于GitCode OpenAPI</p>
</footer>
</body>
</html>
页面完成后,启动手机模拟器,预览效果如下:

当用户没有输入关键词进行检索时,会给出提示信息:

口袋工具逻辑处理
继续完善点击【检索】按钮后的逻辑处理:
document.addEventListener('DOMContentLoaded', function() {
const btnQuery = document.getElementById('btnquery');
const btnTest = document.getElementById('btnTest');
const resultDiv = document.getElementById('result');
const loadingDiv = document.getElementById('loading');
const keyInput = document.getElementById('key');
const categorySelect = document.getElementById('category');
const keywordInput = document.getElementById('keyword');
// GitCode API基础URL
const API_BASE_URL = 'https://api.gitcode.com/api/v5';
// 获取当前用户信息 - 验证token
async function getCurrentUser(token) {
const url = `${API_BASE_URL}/user`;
console.log('获取用户信息URL:', url);
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
console.log('用户信息响应状态:', response.status);
if (!response.ok) {
let errorText = '未知错误';
try {
errorText = await response.text();
} catch (e) {
errorText = '无法读取错误信息';
}
throw new Error(`获取用户信息失败: HTTP ${response.status} - ${errorText}`);
}
const data = await response.json();
console.log('当前用户信息:', data);
return data;
} catch (error) {
console.error('获取用户信息异常:', error);
throw error;
}
}
// 搜索用户
async function searchUsers(token, keyword) {
// 尝试多种可能的端点
const endpoints = [
`${API_BASE_URL}/search/users?q=${encodeURIComponent(keyword)}`,
`${API_BASE_URL}/users?search=${encodeURIComponent(keyword)}`,
`${API_BASE_URL}/users?username=${encodeURIComponent(keyword)}`
];
for (const url of endpoints) {
try {
console.log('尝试搜索用户URL:', url);
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
console.log('搜索用户响应状态:', response.status);
if (response.ok) {
const data = await response.json();
console.log('搜索用户成功,结果:', data);
// 统一数据格式
if (Array.isArray(data)) {
return { items: data, total_count: data.length };
} else if (data.items) {
return data;
} else {
return { items: [data], total_count: 1 };
}
} else if (response.status === 404) {
console.log('端点不存在,尝试下一个');
continue;
}
} catch (error) {
console.log('搜索用户请求异常:', error);
continue;
}
}
throw new Error('所有用户搜索端点都失败了');
}
// 搜索仓库
async function searchProjects(token, keyword) {
// 尝试多种可能的端点
const endpoints = [
`${API_BASE_URL}/search/repositories?q=${encodeURIComponent(keyword)}`,
`${API_BASE_URL}/projects?search=${encodeURIComponent(keyword)}`,
`${API_BASE_URL}/repositories?search=${encodeURIComponent(keyword)}`
];
for (const url of endpoints) {
try {
console.log('尝试搜索仓库URL:', url);
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
console.log('搜索仓库响应状态:', response.status);
if (response.ok) {
const data = await response.json();
console.log('搜索仓库成功,结果:', data);
// 统一数据格式
if (Array.isArray(data)) {
return { items: data, total_count: data.length };
} else if (data.items) {
return data;
} else {
return { items: [data], total_count: 1 };
}
} else if (response.status === 404) {
console.log('端点不存在,尝试下一个');
continue;
}
} catch (error) {
console.log('搜索仓库请求异常:', error);
continue;
}
}
throw new Error('所有仓库搜索端点都失败了');
}
// 显示加载状态
function showLoading() {
loadingDiv.style.display = 'block';
resultDiv.innerHTML = '';
btnQuery.disabled = true;
btnTest.disabled = true;
}
// 隐藏加载状态
function hideLoading() {
loadingDiv.style.display = 'none';
btnQuery.disabled = false;
btnTest.disabled = false;
}
// 显示错误信息
function showError(message) {
resultDiv.innerHTML = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
<strong>错误:</strong> ${message}
</div>
`;
}
// 显示成功信息
function showSuccess(message) {
resultDiv.innerHTML = `
<div class="success-message">
<i class="fas fa-check-circle"></i>
<strong>成功:</strong> ${message}
</div>
`;
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '未知';
try {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN');
} catch (e) {
return '未知';
}
}
// 显示搜索结果
function displayResults(results, category) {
console.log('显示结果函数被调用:', { results, category });
if (!results || (!results.items && !Array.isArray(results))) {
resultDiv.innerHTML = `
<div class="no-results">
<i class="fas fa-inbox"></i>
<p>没有找到相关${category === 'users' ? '用户' : '仓库'}</p>
</div>
`;
return;
}
let resultsHtml = '';
// 统一数据格式处理
let items = [];
let totalCount = 0;
if (Array.isArray(results)) {
items = results;
totalCount = results.length;
} else if (results.items) {
items = results.items;
totalCount = results.total_count || results.items.length;
} else {
items = [results];
totalCount = 1;
}
console.log('处理的数据:', { items, totalCount });
if (items.length === 0) {
resultDiv.innerHTML = `
<div class="no-results">
<i class="fas fa-inbox"></i>
<p>没有找到相关${category === 'users' ? '用户' : '仓库'}</p>
<p style="font-size: 0.8rem; margin-top: 0.5rem; color: #666;">
共搜索到 ${totalCount} 个结果
</p>
</div>
`;
return;
}
// 显示总数信息
resultsHtml += `
<div class="result-count">
<i class="fas fa-chart-bar"></i> 共找到 ${totalCount} 个${category === 'users' ? '用户' : '仓库'}
</div>
`;
if (category === 'users') {
items.forEach(user => {
console.log('处理用户数据:', user);
resultsHtml += `
<div class="result-item">
<img src="${user.avatar_url || 'https://via.placeholder.com/50?text=User'}"
alt="${user.login || user.username || '用户'}"
class="result-avatar"
onerror="this.src='https://via.placeholder.com/50?text=User'">
<div class="result-content">
<div class="result-title">
<a href="${user.html_url || user.web_url || user.url || '#'}" target="_blank">
<i class="fas fa-user"></i> ${user.login || user.username || '未知用户'}
</a>
</div>
<div class="result-desc">
${user.bio || user.description || '该用户暂无简介'}
</div>
<div class="result-meta">
<span><i class="fas fa-id-card"></i> ID: ${user.id || '未知'}</span>
${user.type ? `<span><i class="fas fa-tag"></i> ${user.type}</span>` : ''}
${(user.site_admin || user.is_admin) ? `<span style="color: var(--success-color);"><i class="fas fa-shield-alt"></i> 管理员</span>` : ''}
</div>
</div>
</div>
`;
});
} else if (category === 'projects') {
items.forEach(repo => {
console.log('处理仓库数据:', repo);
resultsHtml += `
<div class="result-item">
<img src="${repo.owner?.avatar_url || repo.avatar_url || 'https://via.placeholder.com/50?text=Repo'}"
alt="${repo.name}"
class="result-avatar"
onerror="this.src='https://via.placeholder.com/50?text=Repo'">
<div class="result-content">
<div class="result-title">
<a href="${repo.html_url || repo.web_url || repo.url || '#'}" target="_blank">
<i class="fas fa-code-branch"></i> ${repo.full_name || repo.name_with_namespace || repo.name || '未知项目'}
</a>
</div>
<div class="result-desc">
${repo.description || '该项目暂无描述'}
</div>
<div class="result-meta">
<span><i class="fas fa-star"></i> ${repo.stargazers_count || repo.star_count || 0}</span>
<span><i class="fas fa-code-fork"></i> ${repo.forks_count || 0}</span>
<span><i class="fas fa-eye"></i> ${repo.watchers_count || repo.watch_count || 0}</span>
<span><i class="fas fa-code"></i> ${repo.language || '未知'}</span>
${repo.updated_at || repo.last_activity_at ? `<span><i class="fas fa-clock"></i> ${formatDate(repo.updated_at || repo.last_activity_at)}</span>` : ''}
</div>
</div>
</div>
`;
});
}
console.log('生成的HTML长度:', resultsHtml.length);
resultDiv.innerHTML = resultsHtml;
}
// 检索按钮点击事件
btnQuery.addEventListener('click', async function() {
// 获取输入值
const token = keyInput.value.trim();
const category = categorySelect.value;
const keyword = keywordInput.value.trim();
// 验证输入
if (!token) {
showError('请输入访问令牌');
return;
}
if (!keyword) {
showError('请输入搜索关键词');
return;
}
// 显示加载状态
showLoading();
try {
console.log('=== 开始GitCode API请求 ===');
// 1. 先验证token
console.log('步骤1: 验证token...');
const userInfo = await getCurrentUser(token);
console.log('✅ Token验证成功,当前用户:', userInfo.login || userInfo.username);
// 2. 执行搜索
console.log('步骤2: 执行搜索...');
let results;
if (category === 'users') {
results = await searchUsers(token, keyword);
} else {
results = await searchProjects(token, keyword);
}
console.log('✅ 搜索完成,准备显示结果');
// 3. 显示结果
displayResults(results, category);
} catch (error) {
console.error('❌ API请求失败:', error);
showError(`请求失败: ${error.message}`);
} finally {
// 隐藏加载状态
hideLoading();
}
});
// 测试连接按钮点击事件
btnTest.addEventListener('click', async function() {
const token = keyInput.value.trim();
if (!token) {
showError('请输入访问令牌');
return;
}
showLoading();
try {
console.log('=== 测试GitCode API连接 ===');
const userInfo = await getCurrentUser(token);
showSuccess(`
✅ 连接测试成功!
<div style="margin-top: 10px;">
<strong>用户信息:</strong><br>
- 用户名: ${userInfo.login || userInfo.username || '未知'}<br>
- 姓名: ${userInfo.name || '未设置'}<br>
- ID: ${userInfo.id || '未知'}<br>
${userInfo.email ? `- 邮箱: ${userInfo.email}<br>` : ''}
</div>
<div style="margin-top: 10px;">
<strong>API状态:</strong> 正常
</div>
`);
} catch (error) {
console.error('连接测试失败:', error);
showError(`
❌ 连接测试失败
<div style="margin-top: 10px;">
<strong>错误信息:</strong> ${error.message}
</div>
<div style="margin-top: 10px;">
<strong>可能的原因:</strong><br>
1. Token无效或已过期<br>
2. Token权限不足<br>
3. 网络连接问题<br>
4. 鸿蒙模拟器网络限制
</div>
`);
} finally {
hideLoading();
}
});
// 添加示例搜索关键词
keywordInput.placeholder = "例如: john, react, vue 等";
// 添加回车键搜索支持
keywordInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
btnQuery.click();
}
});
// 添加调试信息显示
const debugInfo = document.createElement('div');
debugInfo.style.cssText = 'position: fixed; bottom: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 10px; border-radius: 5px; font-size: 12px; z-index: 1000;';
debugInfo.innerHTML = '调试模式已开启';
document.body.appendChild(debugInfo);
});
说明: 使用v5版本的API,确保功能完整性和稳定性。
项目运行效果
1. 初始界面
页面加载完成后,用户首先看到简洁的搜索界面,包含清晰的输入区域和引导信息。
2. 令牌验证测试
用户输入GitCode访问令牌后,可点击"测试连接"按钮验证令牌有效性:
-
成功:显示用户基本信息,确认API连接正常
-
失败:详细列出可能的原因和解决方案
3. 用户搜索演示
搜索流程:
-
选择"用户"类别
-
输入用户名关键词(如"john")
-
点击"检索"按钮
预期结果:
-
显示匹配用户的头像、用户名、简介等信息
-
展示搜索结果总数
-
提供用户详情链接
运行效果如下:

这里报 HTTP 400 错误,根据 gitcode openapi 文档,400 是网络限制问题,此处需要修改 module.json5 增加如下代码:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
重新访问选择【用户】输入关键词,点击【检索】按钮,运行效果如下:

4. 仓库搜索演示
搜索流程:
-
选择"仓库"类别
-
输入仓库关键词(如"react")
-
点击"检索"按钮
预期结果:
-
显示匹配仓库的名称、描述、拥有者信息
-
展示星标数、复刻数、编程语言等关键指标
-
提供仓库详情链接
界面示例:
选择【仓库】输入关键词,点击【检索】按钮,运行效果如下:

总结
通过本项目的开发,我们成功构建了一个功能完善、界面美观的GitCode口袋工具。该项目不仅展示了Cordova开发混合应用的流程,也体现了良好的API集成实践和用户体验设计理念。
项目的核心价值在于:
-
便捷性:为开发者提供快速访问GitCode平台的方式
-
实用性:涵盖用户和仓库两大核心搜索功能
-
可扩展性:架构设计支持后续功能扩展
-
跨平台性:一次开发,多端部署
无论是作为学习项目还是实际应用,这个GitCode口袋工具都具备良好的参考价值和实用价值。开发者可以根据自身需求,在此基础之上进行进一步的优化和扩展,打造更加个性化的代码搜索工具。
更多推荐


所有评论(0)