forked from bai/curriculum-project-hub
feat(filelib): 项目新增修改历史 tab 并隐藏 version hash id
This commit is contained in:
@@ -128,9 +128,6 @@
|
||||
<div class="flex shrink-0 items-center justify-between border-b border-line-soft px-6 py-4">
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<span class="text-[15px] font-semibold text-ink truncate">{filename}</span>
|
||||
{#if file}
|
||||
<span class="file-meta">@ {file.version}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
{#if file}
|
||||
@@ -183,9 +180,17 @@
|
||||
<Modal title="版本历史" onclose={() => (showHistory = false)}>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
{#each history as v (v.version)}
|
||||
<div class="border-t border-line-soft py-2 text-xs first:border-t-0">
|
||||
<span class="font-mono text-accent">{v.version}</span> {v.message}
|
||||
<div class="text-ink-3">{new Date(v.committedAt).toLocaleString("zh-CN")}{v.author ? " · " + v.author : ""}</div>
|
||||
<div class="flex items-center gap-2.5 border-t border-line-soft py-2.5 first:border-t-0">
|
||||
<span
|
||||
class="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent text-[11px] font-semibold text-white"
|
||||
aria-hidden="true"
|
||||
>{(v.author ?? "?").slice(0, 1).toUpperCase()}</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[12.5px] text-ink">{v.message}</p>
|
||||
<p class="text-[11px] text-ink-3">
|
||||
{#if v.author}<span>{v.author}</span> · {/if}{new Date(v.committedAt).toLocaleString("zh-CN")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* 项目修改历史 tab:展示所有文件的提交记录(新→旧)。
|
||||
* 默认只显示 commit message + 作者头像 + 时间,点击可展开查看修改的文件。
|
||||
*/
|
||||
import { api } from "./api.js";
|
||||
import { toastErr } from "./stores.js";
|
||||
import type { ProjectCommitInfo, NodeDetail } from "./types.js";
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
let { node }: { node: NodeDetail } = $props();
|
||||
|
||||
let history = $state<ProjectCommitInfo[] | null>(null);
|
||||
let loadError = $state<string | null>(null);
|
||||
let limit = $state(50);
|
||||
let expanded = $state<Set<string>>(new Set());
|
||||
|
||||
async function loadHistory(): Promise<void> {
|
||||
try {
|
||||
const r = await api<{ history: ProjectCommitInfo[] }>(
|
||||
`/database/api/projects/${node.id}/history?limit=${limit}`,
|
||||
);
|
||||
history = r.history;
|
||||
loadError = null;
|
||||
} catch (e) {
|
||||
loadError = e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
void node.id;
|
||||
void loadHistory();
|
||||
});
|
||||
|
||||
function toggle(version: string): void {
|
||||
const next = new Set(expanded);
|
||||
if (next.has(version)) next.delete(version);
|
||||
else next.add(version);
|
||||
expanded = next;
|
||||
}
|
||||
|
||||
function loadMore(): void {
|
||||
limit += 50;
|
||||
void loadHistory();
|
||||
}
|
||||
|
||||
function authorInitial(author: string | undefined): string {
|
||||
return (author ?? "?").slice(0, 1).toUpperCase();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="panel">
|
||||
<div class="mb-3 section-title">修改历史</div>
|
||||
|
||||
{#if history === null && loadError === null}
|
||||
<div class="quiet py-5 text-center">加载中…</div>
|
||||
{:else if loadError}
|
||||
<div class="py-5 text-center text-xs text-danger">{loadError}</div>
|
||||
{:else if history && history.length === 0}
|
||||
<div class="quiet py-5 text-center">暂无提交记录</div>
|
||||
{:else if history}
|
||||
<div>
|
||||
{#each history as commit (commit.version)}
|
||||
{@const isOpen = expanded.has(commit.version)}
|
||||
<div class="border-t border-line-soft first:border-t-0">
|
||||
<button
|
||||
class="flex w-full items-center gap-2.5 py-3 text-left transition hover:bg-hover rounded-md px-1.5 -mx-1.5"
|
||||
onclick={() => toggle(commit.version)}
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<span
|
||||
class="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent text-[12px] font-semibold text-white"
|
||||
aria-hidden="true"
|
||||
>{authorInitial(commit.author)}</span>
|
||||
<!-- 消息与时间 -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[13px] text-ink">{commit.message}</p>
|
||||
<p class="mt-0.5 text-[11.5px] text-ink-3">
|
||||
{#if commit.author}<span>{commit.author}</span> · {/if}{new Date(commit.committedAt).toLocaleString("zh-CN")}
|
||||
</p>
|
||||
</div>
|
||||
<!-- 展开指示 -->
|
||||
<span class="shrink-0 text-ink-3 transition {isOpen ? 'rotate-90' : ''}">
|
||||
<Icon name="chevron" size={12} />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="pb-3 pl-[46px]">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each commit.files as filePath (filePath)}
|
||||
<span class="inline-flex items-center gap-1 rounded-md bg-hover px-1.5 py-0.5 font-mono text-[11px] text-ink-2">
|
||||
<Icon name="file" size={10} />{filePath}
|
||||
</span>
|
||||
{/each}
|
||||
{#if commit.files.length === 0}
|
||||
<span class="text-[11.5px] text-ink-3">无文件变更信息</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if history.length >= limit}
|
||||
<div class="mt-3 flex justify-center">
|
||||
<button class="btn" onclick={loadMore}>加载更多</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -5,10 +5,11 @@
|
||||
import OverviewPanel from "./OverviewPanel.svelte";
|
||||
import FilesPanel from "./FilesPanel.svelte";
|
||||
import GrantsPanel from "./GrantsPanel.svelte";
|
||||
import HistoryPanel from "./HistoryPanel.svelte";
|
||||
import Modal from "./Modal.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
type Tab = "detail" | "files" | "grants";
|
||||
type Tab = "detail" | "files" | "history" | "grants";
|
||||
let tab = $state<Tab>("detail");
|
||||
let showCreateChild = $state(false);
|
||||
let newName = $state("");
|
||||
@@ -20,11 +21,12 @@
|
||||
const canManage = $derived(node?.role === "MANAGE");
|
||||
const canEdit = $derived(canManage || node?.role === "EDIT");
|
||||
|
||||
// 与旧 libraryBrowser 的 tab 组装一致:概览恒有;文件仅 PROJECT;授权仅 MANAGE
|
||||
// 与旧 libraryBrowser 的 tab 组装一致:概览恒有;文件仅 PROJECT;修改历史仅 PROJECT;授权仅 MANAGE
|
||||
// (FOLDER 也有授权 —— 它虽是透明组织节点,授权仍挂在节点上,ADR-0021)。
|
||||
const tabs = $derived.by((): ReadonlyArray<readonly [Tab, string]> => {
|
||||
const out: Array<readonly [Tab, string]> = [["detail", "概览"]];
|
||||
if (node?.kind === "PROJECT") out.push(["files", "文件"]);
|
||||
if (node?.kind === "PROJECT") out.push(["history", "修改历史"]);
|
||||
if (canManage) out.push(["grants", "授权"]);
|
||||
return out;
|
||||
});
|
||||
@@ -129,6 +131,8 @@
|
||||
<GrantsPanel {node} />
|
||||
{:else if tab === "files" && node.kind === "PROJECT"}
|
||||
<FilesPanel {node} />
|
||||
{:else if tab === "history" && node.kind === "PROJECT"}
|
||||
<HistoryPanel {node} />
|
||||
{:else}
|
||||
<OverviewPanel {node} />
|
||||
{#if node.kind === "FOLDER"}
|
||||
|
||||
@@ -63,6 +63,15 @@ export interface VersionInfo {
|
||||
readonly committedAt: string;
|
||||
}
|
||||
|
||||
/** 项目级提交历史条目(包含受影响文件路径)。 */
|
||||
export interface ProjectCommitInfo {
|
||||
readonly version: string;
|
||||
readonly message: string;
|
||||
readonly author?: string;
|
||||
readonly committedAt: string;
|
||||
readonly files: readonly string[];
|
||||
}
|
||||
|
||||
export interface ExportJob {
|
||||
readonly id: string;
|
||||
readonly nodeId: string;
|
||||
|
||||
Reference in New Issue
Block a user