update 结果查看页面支持排序

This commit is contained in:
dingyufei
2025-07-23 18:01:32 +08:00
parent 99cbd80d15
commit e3fc83c522
2 changed files with 48 additions and 7 deletions

View File

@@ -25,6 +25,15 @@ document.addEventListener('DOMContentLoaded', function() {
<input type="checkbox" id="recommended-only-checkbox"> <input type="checkbox" id="recommended-only-checkbox">
仅看AI推荐 仅看AI推荐
</label> </label>
<select id="sort-by-selector">
<option value="crawl_time">按爬取时间</option>
<option value="publish_time">按发布时间</option>
<option value="price">按价格</option>
</select>
<select id="sort-order-selector">
<option value="desc">降序</option>
<option value="asc">升序</option>
</select>
<button id="refresh-results-btn" class="control-button">🔄 刷新</button> <button id="refresh-results-btn" class="control-button">🔄 刷新</button>
</div> </div>
<div id="results-grid-container"> <div id="results-grid-container">
@@ -191,12 +200,14 @@ document.addEventListener('DOMContentLoaded', function() {
} }
} }
async function fetchResultContent(filename, recommendedOnly) { async function fetchResultContent(filename, recommendedOnly, sortBy, sortOrder) {
try { try {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: 1, page: 1,
limit: 100, // Fetch a decent number of items limit: 100, // Fetch a decent number of items
recommended_only: recommendedOnly recommended_only: recommendedOnly,
sort_by: sortBy,
sort_order: sortOrder
}); });
const response = await fetch(`/api/results/${filename}?${params}`); const response = await fetch(`/api/results/${filename}?${params}`);
if (!response.ok) throw new Error(`无法获取文件 ${filename} 的内容`); if (!response.ok) throw new Error(`无法获取文件 ${filename} 的内容`);
@@ -288,6 +299,8 @@ document.addEventListener('DOMContentLoaded', function() {
const recommendationText = isRecommended ? '推荐' : (ai.is_recommended === false ? '不推荐' : '待定'); const recommendationText = isRecommended ? '推荐' : (ai.is_recommended === false ? '不推荐' : '待定');
const imageUrl = (info.商品图片列表 && info.商品图片列表[0]) ? info.商品图片列表[0] : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; const imageUrl = (info.商品图片列表 && info.商品图片列表[0]) ? info.商品图片列表[0] : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
const crawlTime = item.爬取时间 ? new Date(item.爬取时间).toLocaleString('sv-SE').slice(0, 16) : '未知';
const publishTime = info.发布时间 || '未知';
return ` return `
<div class="result-card" data-item='${JSON.stringify(item)}'> <div class="result-card" data-item='${JSON.stringify(item)}'>
@@ -303,6 +316,10 @@ document.addEventListener('DOMContentLoaded', function() {
</div> </div>
<div class="card-footer"> <div class="card-footer">
<span class="seller-info">卖家: ${info.卖家昵称 || seller.卖家昵称 || '未知'}</span> <span class="seller-info">卖家: ${info.卖家昵称 || seller.卖家昵称 || '未知'}</span>
<div class="time-info">
<p>发布于: ${publishTime}</p>
<p>抓取于: ${crawlTime}</p>
</div>
<span> <span>
<a href="${info.商品链接 || '#'}" target="_blank" class="action-btn">查看详情</a> <a href="${info.商品链接 || '#'}" target="_blank" class="action-btn">查看详情</a>
</span> </span>
@@ -400,12 +417,16 @@ document.addEventListener('DOMContentLoaded', function() {
async function fetchAndRenderResults() { async function fetchAndRenderResults() {
const selector = document.getElementById('result-file-selector'); const selector = document.getElementById('result-file-selector');
const checkbox = document.getElementById('recommended-only-checkbox'); const checkbox = document.getElementById('recommended-only-checkbox');
const sortBySelector = document.getElementById('sort-by-selector');
const sortOrderSelector = document.getElementById('sort-order-selector');
const container = document.getElementById('results-grid-container'); const container = document.getElementById('results-grid-container');
if (!selector || !checkbox || !container) return; if (!selector || !checkbox || !container || !sortBySelector || !sortOrderSelector) return;
const selectedFile = selector.value; const selectedFile = selector.value;
const recommendedOnly = checkbox.checked; const recommendedOnly = checkbox.checked;
const sortBy = sortBySelector.value;
const sortOrder = sortOrderSelector.value;
if (!selectedFile) { if (!selectedFile) {
container.innerHTML = '<p>请先选择一个结果文件。</p>'; container.innerHTML = '<p>请先选择一个结果文件。</p>';
@@ -413,7 +434,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
container.innerHTML = '<p>正在加载结果...</p>'; container.innerHTML = '<p>正在加载结果...</p>';
const data = await fetchResultContent(selectedFile, recommendedOnly); const data = await fetchResultContent(selectedFile, recommendedOnly, sortBy, sortOrder);
container.innerHTML = renderResultsGrid(data); container.innerHTML = renderResultsGrid(data);
} }
@@ -421,12 +442,16 @@ document.addEventListener('DOMContentLoaded', function() {
const selector = document.getElementById('result-file-selector'); const selector = document.getElementById('result-file-selector');
const checkbox = document.getElementById('recommended-only-checkbox'); const checkbox = document.getElementById('recommended-only-checkbox');
const refreshBtn = document.getElementById('refresh-results-btn'); const refreshBtn = document.getElementById('refresh-results-btn');
const sortBySelector = document.getElementById('sort-by-selector');
const sortOrderSelector = document.getElementById('sort-order-selector');
const fileData = await fetchResultFiles(); const fileData = await fetchResultFiles();
if (fileData && fileData.files && fileData.files.length > 0) { if (fileData && fileData.files && fileData.files.length > 0) {
selector.innerHTML = fileData.files.map(f => `<option value="${f}">${f}</option>`).join(''); selector.innerHTML = fileData.files.map(f => `<option value="${f}">${f}</option>`).join('');
selector.addEventListener('change', fetchAndRenderResults); selector.addEventListener('change', fetchAndRenderResults);
checkbox.addEventListener('change', fetchAndRenderResults); checkbox.addEventListener('change', fetchAndRenderResults);
sortBySelector.addEventListener('change', fetchAndRenderResults);
sortOrderSelector.addEventListener('change', fetchAndRenderResults);
refreshBtn.addEventListener('click', fetchAndRenderResults); refreshBtn.addEventListener('click', fetchAndRenderResults);
// Initial load // Initial load
await fetchAndRenderResults(); await fetchAndRenderResults();

View File

@@ -327,9 +327,9 @@ async def list_result_files():
@app.get("/api/results/{filename}") @app.get("/api/results/{filename}")
async def get_result_file_content(filename: str, page: int = 1, limit: int = 20, recommended_only: bool = False): async def get_result_file_content(filename: str, page: int = 1, limit: int = 20, recommended_only: bool = False, sort_by: str = "crawl_time", sort_order: str = "desc"):
""" """
读取指定的 .jsonl 文件内容,支持分页筛选。 读取指定的 .jsonl 文件内容,支持分页筛选和排序
""" """
if not filename.endswith(".jsonl") or "/" in filename or ".." in filename: if not filename.endswith(".jsonl") or "/" in filename or ".." in filename:
raise HTTPException(status_code=400, detail="无效的文件名。") raise HTTPException(status_code=400, detail="无效的文件名。")
@@ -353,7 +353,23 @@ async def get_result_file_content(filename: str, page: int = 1, limit: int = 20,
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"读取结果文件时出错: {e}") raise HTTPException(status_code=500, detail=f"读取结果文件时出错: {e}")
results.reverse() # --- Sorting logic ---
def get_sort_key(item):
info = item.get("商品信息", {})
if sort_by == "publish_time":
# Handles "未知时间" by placing it at the end/start depending on order
return info.get("发布时间", "0000-00-00 00:00")
elif sort_by == "price":
price_str = str(info.get("当前售价", "0")).replace("¥", "").replace(",", "").strip()
try:
return float(price_str)
except (ValueError, TypeError):
return 0.0 # Default for unparsable prices
else: # default to crawl_time
return item.get("爬取时间", "")
is_reverse = (sort_order == "desc")
results.sort(key=get_sort_key, reverse=is_reverse)
total_items = len(results) total_items = len(results)
start = (page - 1) * limit start = (page - 1) * limit