initial: blog-app snapshot
This commit is contained in:
95
static/js/pages/project-detail.js
Normal file
95
static/js/pages/project-detail.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/* 项目详情页逻辑: 项目头部 + 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();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user