diff --git a/ghproxy/public/search.html b/ghproxy/public/search.html
index 2eaf62e..71c2d8e 100644
--- a/ghproxy/public/search.html
+++ b/ghproxy/public/search.html
@@ -512,16 +512,70 @@
prevButton.disabled = currentPage <= 1;
nextButton.disabled = currentPage >= totalPages;
- // 添加页码显示
+ // 添加页码显示和快速跳转
const paginationDiv = document.querySelector('.pagination');
- const pageInfo = document.getElementById('pageInfo');
+ let pageInfo = document.getElementById('pageInfo');
if (!pageInfo) {
- const infoSpan = document.createElement('span');
- infoSpan.id = 'pageInfo';
- infoSpan.style.margin = '0 10px';
- paginationDiv.insertBefore(infoSpan, nextButton);
+ const container = document.createElement('div');
+ container.id = 'pageInfo';
+ container.style.margin = '0 10px';
+ container.style.display = 'flex';
+ container.style.alignItems = 'center';
+ container.style.gap = '10px';
+
+ // 页码显示
+ const pageText = document.createElement('span');
+ pageText.id = 'pageText';
+
+ // 跳转输入框
+ const jumpInput = document.createElement('input');
+ jumpInput.type = 'number';
+ jumpInput.min = '1';
+ jumpInput.id = 'jumpPage';
+ jumpInput.style.width = '60px';
+ jumpInput.style.padding = '4px';
+ jumpInput.style.borderRadius = '4px';
+ jumpInput.style.border = '1px solid var(--border-color)';
+ jumpInput.style.backgroundColor = 'var(--inputcolor)';
+ jumpInput.style.color = 'var(--inputcolor-font)';
+
+ // 跳转按钮
+ const jumpButton = document.createElement('button');
+ jumpButton.textContent = '跳转';
+ jumpButton.className = 'btn search-button';
+ jumpButton.style.padding = '4px 8px';
+ jumpButton.onclick = () => {
+ const page = parseInt(jumpInput.value);
+ if (page && page >= 1 && page <= totalPages) {
+ currentPage = page;
+ performSearch();
+ } else {
+ showToast('请输入有效的页码');
+ }
+ };
+
+ container.appendChild(pageText);
+ container.appendChild(jumpInput);
+ container.appendChild(jumpButton);
+
+ // 插入到分页区域
+ paginationDiv.insertBefore(container, nextButton);
+ pageInfo = container;
}
- document.getElementById('pageInfo').textContent = `第 ${currentPage} / ${totalPages} 页`;
+
+ // 更新页码显示
+ const pageText = document.getElementById('pageText');
+ pageText.textContent = `第 ${currentPage} / ${totalPages || 1} 页 共 ${totalPages || 1} 页`;
+
+ // 更新跳转输入框
+ const jumpInput = document.getElementById('jumpPage');
+ if (jumpInput) {
+ jumpInput.max = totalPages;
+ jumpInput.value = currentPage;
+ }
+
+ // 显示或隐藏分页区域
+ paginationDiv.style.display = totalPages > 1 ? 'flex' : 'none';
}
function showSearchResults() {
@@ -560,11 +614,7 @@
console.log('搜索响应:', data);
// 更新总页数和分页状态
- if (typeof data.count === 'number') {
- totalPages = Math.ceil(data.count / 25);
- } else {
- totalPages = data.results ? Math.ceil(data.results.length / 25) : 1;
- }
+ totalPages = Math.ceil(data.count / 25);
updatePagination();
displayResults(data.results);
diff --git a/ghproxy/search.go b/ghproxy/search.go
index 90093ae..43a484f 100644
--- a/ghproxy/search.go
+++ b/ghproxy/search.go
@@ -223,18 +223,45 @@ func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*Se
var result *SearchResult
var lastErr error
+ // 判断是否是用户/仓库格式的搜索
+ isUserRepo := strings.Contains(query, "/")
+
for retries := 3; retries > 0; retries-- {
- result, lastErr = trySearchDockerHub(ctx, query, page, pageSize)
+ if isUserRepo {
+ // 对于用户/仓库格式,尝试精确搜索和模糊搜索
+ parts := strings.Split(query, "/")
+ if len(parts) == 2 {
+ // 先尝试精确搜索
+ result, lastErr = trySearchDockerHub(ctx, query, page, pageSize)
+ if lastErr == nil && len(result.Results) == 0 {
+ // 如果精确搜索没有结果,尝试模糊搜索
+ result, lastErr = trySearchDockerHub(ctx, parts[1], page, pageSize)
+ if lastErr == nil {
+ // 过滤出属于指定用户的结果
+ filteredResults := make([]Repository, 0)
+ for _, repo := range result.Results {
+ if strings.EqualFold(repo.Namespace, parts[0]) ||
+ strings.EqualFold(repo.RepoOwner, parts[0]) {
+ filteredResults = append(filteredResults, repo)
+ }
+ }
+ result.Results = filteredResults
+ result.Count = len(filteredResults)
+ }
+ }
+ }
+ } else {
+ result, lastErr = trySearchDockerHub(ctx, query, page, pageSize)
+ }
+
if lastErr == nil {
break
}
- // 判断是否需要重试
if !isRetryableError(lastErr) {
return nil, fmt.Errorf("搜索失败: %v", lastErr)
}
- // 等待后重试
time.Sleep(time.Second * time.Duration(4-retries))
}
@@ -316,7 +343,6 @@ func trySearchDockerHub(ctx context.Context, query string, page, pageSize int) (
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
- // 特殊处理常见错误
switch resp.StatusCode {
case http.StatusTooManyRequests:
return nil, fmt.Errorf("请求过于频繁,请稍后重试")
@@ -342,7 +368,6 @@ func trySearchDockerHub(ctx context.Context, query string, page, pageSize int) (
if !strings.Contains(result.Results[i].Name, "/") {
result.Results[i].Name = "library/" + result.Results[i].Name
}
- // 设置命名空间为 library
result.Results[i].Namespace = "library"
} else {
// 从 repo_name 中提取 namespace
@@ -350,7 +375,7 @@ func trySearchDockerHub(ctx context.Context, query string, page, pageSize int) (
if len(parts) > 1 {
result.Results[i].Namespace = parts[0]
result.Results[i].Name = parts[1]
- } else {
+ } else if result.Results[i].RepoOwner != "" {
result.Results[i].Namespace = result.Results[i].RepoOwner
}
}
@@ -452,11 +477,6 @@ func RegisterSearchRoute(r *gin.Engine) {
fmt.Sscanf(ps, "%d", &pageSize)
}
- // 如果是搜索官方镜像
- if !strings.Contains(query, "/") {
- query = strings.ToLower(query)
- }
-
fmt.Printf("搜索请求: query=%s, page=%d, pageSize=%d\n", query, page, pageSize)
result, err := searchDockerHub(c.Request.Context(), query, page, pageSize)
@@ -466,23 +486,6 @@ func RegisterSearchRoute(r *gin.Engine) {
return
}
- // 过滤搜索结果,只保留相关的镜像
- filteredResults := make([]Repository, 0)
- searchTerm := strings.ToLower(strings.TrimPrefix(query, "library/"))
-
- for _, repo := range result.Results {
- repoName := strings.ToLower(repo.Name)
- // 如果是精确匹配或者以搜索词开头,或者包含 "searchTerm/searchTerm"
- if repoName == searchTerm ||
- strings.HasPrefix(repoName, searchTerm+"/") ||
- strings.Contains(repoName, "/"+searchTerm) {
- filteredResults = append(filteredResults, repo)
- }
- }
-
- result.Results = filteredResults
- result.Count = len(filteredResults)
-
c.JSON(http.StatusOK, result)
})