96 lines
4.3 KiB
JavaScript
96 lines
4.3 KiB
JavaScript
/* 项目详情页逻辑: 项目头部 + release 时间线 + 最近 issues. */
|
|
|
|
import { fetchJson, escapeHtml, truncate, formatDate } from "../utils.js";
|
|
|
|
export function initProjectDetailPage(projectId) {
|
|
projectId = Number.parseInt(projectId, 10);
|
|
if (Number.isNaN(projectId)) {
|
|
console.error("project-detail: invalid project id");
|
|
return;
|
|
}
|
|
async function loadProject() {
|
|
const header = document.getElementById("projectHeader");
|
|
try {
|
|
const p = await fetchJson(`/api/projects/${projectId}`);
|
|
header.innerHTML = `
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<h1 class="card-title h3">${escapeHtml(p.name)}
|
|
<small class="text-muted fs-6">${escapeHtml(p.full_name || "")}</small>
|
|
</h1>
|
|
<p class="card-text">${escapeHtml(p.description || "暂无描述")}</p>
|
|
<p class="mb-0">
|
|
<span class="badge bg-warning text-dark">⭐ Stars: ${p.stars ?? 0}</span>
|
|
<span class="badge bg-secondary">🍴 Forks: ${p.forks ?? 0}</span>
|
|
<span class="badge bg-info text-dark">${escapeHtml(p.language || "N/A")}</span>
|
|
<span class="badge bg-light text-dark">更新于 ${formatDate(p.updated_at)}</span>
|
|
</p>
|
|
</div>
|
|
</div>`;
|
|
} catch (err) {
|
|
console.error("加载项目信息失败:", err);
|
|
header.innerHTML =
|
|
'<div class="empty-hint empty-hint-error">加载失败, <button type="button" class="btn btn-sm btn-outline-danger js-retry-btn">点击重试</button></div>';
|
|
}
|
|
}
|
|
|
|
async function loadReleases() {
|
|
const list = document.getElementById("releaseTimeline");
|
|
try {
|
|
const releases = await fetchJson(`/api/projects/${projectId}/releases`);
|
|
if (!releases.length) {
|
|
list.innerHTML = '<li class="list-group-item empty-hint">暂无 release</li>';
|
|
return;
|
|
}
|
|
list.innerHTML = releases
|
|
.map(
|
|
(r) => `
|
|
<li class="list-group-item">
|
|
<div class="d-flex justify-content-between align-items-baseline">
|
|
<strong>${escapeHtml(r.tag_name || r.name || "release")}</strong>
|
|
<small class="text-muted">${formatDate(r.published_at)}</small>
|
|
</div>
|
|
<div class="text-muted small mt-1">${escapeHtml(truncate(r.body, 200))}</div>
|
|
</li>`
|
|
)
|
|
.join("");
|
|
} catch (err) {
|
|
console.error("加载 releases 失败:", err);
|
|
list.innerHTML =
|
|
'<li class="list-group-item empty-hint empty-hint-error">加载失败, <button type="button" class="btn btn-sm btn-outline-danger js-retry-btn">点击重试</button></li>';
|
|
}
|
|
}
|
|
|
|
async function loadIssues() {
|
|
const tbody = document.getElementById("issueTableBody");
|
|
try {
|
|
const issues = await fetchJson(`/api/projects/${projectId}/issues`);
|
|
if (!issues.length) {
|
|
tbody.innerHTML = '<tr><td colspan="4" class="empty-hint">暂无 issue</td></tr>';
|
|
return;
|
|
}
|
|
tbody.innerHTML = issues
|
|
.map(
|
|
(i) => `
|
|
<tr>
|
|
<td>${escapeHtml(truncate(i.title, 60))}</td>
|
|
<td><span class="badge ${i.state === "open" ? "badge-state-open" : "badge-state-closed"}">${escapeHtml(i.state || "")}</span></td>
|
|
<td>${formatDate(i.created_at)}</td>
|
|
<td>${escapeHtml(i.user || "-")}</td>
|
|
</tr>`
|
|
)
|
|
.join("");
|
|
} catch (err) {
|
|
console.error("加载 issues 失败:", err);
|
|
tbody.innerHTML =
|
|
'<tr><td colspan="4" class="empty-hint empty-hint-error">加载失败, <button type="button" class="btn btn-sm btn-outline-danger js-retry-btn">点击重试</button></td></tr>';
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
loadProject();
|
|
loadReleases();
|
|
loadIssues();
|
|
});
|
|
}
|