feat(tasktree): restore derived outline numbering

This commit is contained in:
root
2026-07-21 10:14:38 +02:00
parent 70e47f0253
commit 38990c4e81
6 changed files with 63 additions and 12 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ import { fetchJson } from "@/api/client";
import type { ApiResult } from "@/types";
export type TaskTreeGroup = { id: string; name: string; description: string; createdAt: string; updatedAt: string };
export type TaskTreeTask = { id: string; groupId: string; parentId: string | null; kind: "task" | "subtask" | "subsubtask"; title: string; description: string; status: string; startAt: string | null; dueAt: string | null; sortOrder: number; createdAt: string; updatedAt: string };
export type TaskTreeTask = { id: string; groupId: string; parentId: string | null; kind: "task" | "subtask" | "subsubtask"; outlineRef: string; title: string; description: string; status: string; startAt: string | null; dueAt: string | null; sortOrder: number; createdAt: string; updatedAt: string };
export type TaskTreeMilestone = { id: string; groupId: string; taskId: string | null; title: string; occursAt: string; createdAt: string };
export type TaskTreeReport = { id: string; taskId: string; title: string; body: string; status: string; createdAt: string };
export type TaskTreeTimeline = { group: TaskTreeGroup; tasks: TaskTreeTask[]; milestones: TaskTreeMilestone[]; reports: TaskTreeReport[] };
@@ -94,7 +94,7 @@ watch(() => props.task.id, revealCurrentTask);
<template>
<div class="tasktree-detail-panel" :data-mode="mode">
<header v-if="mode === 'docked'" class="tasktree-docked-header">
<div><small>{{ kindLabel(task.kind) }}</small><strong :title="plainTitle(task)">{{ plainTitle(task) }}</strong></div>
<div><small>{{ task.outlineRef }} · {{ kindLabel(task.kind) }}</small><strong :title="plainTitle(task)">{{ plainTitle(task) }}</strong></div>
<button type="button" title="切换到弹窗" aria-label="切换到弹窗" @click="emit('mode', 'dialog')"><Maximize2 :size="16" aria-hidden="true" /></button>
<button type="button" title="关闭详情" aria-label="关闭详情" @click="emit('close')"><X :size="17" aria-hidden="true" /></button>
</header>
@@ -108,8 +108,8 @@ watch(() => props.task.id, revealCurrentTask);
:class="[`level-${item.level}`, `relation-${item.relation}`, { current: item.relation === 'current' }]"
:data-current-task="item.relation === 'current' ? 'true' : undefined"
>
<button v-if="item.relation !== 'current'" type="button" @click="emit('select', item.task)"><small>{{ relationLabel(item.relation) }} · {{ kindLabel(item.task.kind) }}</small><strong>{{ plainTitle(item.task) }}</strong></button>
<button v-else class="current" type="button" aria-current="true"><small>{{ relationLabel(item.relation) }} · {{ kindLabel(item.task.kind) }}</small><strong>{{ plainTitle(item.task) }}</strong></button>
<button v-if="item.relation !== 'current'" type="button" @click="emit('select', item.task)"><small>{{ item.task.outlineRef }} · {{ relationLabel(item.relation) }} · {{ kindLabel(item.task.kind) }}</small><strong>{{ plainTitle(item.task) }}</strong></button>
<button v-else class="current" type="button" aria-current="true"><small>{{ item.task.outlineRef }} · {{ relationLabel(item.relation) }} · {{ kindLabel(item.task.kind) }}</small><strong>{{ plainTitle(item.task) }}</strong></button>
</li>
</ol>
</aside>
@@ -125,7 +125,7 @@ watch(() => props.task.id, revealCurrentTask);
<output>{{ taskIndex + 1 }} / {{ tasks.length }}</output>
<button type="button" :disabled="!nextTask" title="下一个任务" aria-label="下一个任务" @click="nextTask && emit('select', nextTask)"><ChevronRight :size="15" aria-hidden="true" /></button>
</div>
<span>开始 <strong>{{ dateLabel(task.startAt) }}</strong></span><span>截止 <strong>{{ dateLabel(task.dueAt) }}</strong></span><span>时区 <strong>{{ displayTimeLabel() }}</strong></span><span>状态 <strong>{{ task.status }}</strong></span>
<span>编号 <strong>{{ task.outlineRef }}</strong></span><span>开始 <strong>{{ dateLabel(task.startAt) }}</strong></span><span>截止 <strong>{{ dateLabel(task.dueAt) }}</strong></span><span>时区 <strong>{{ displayTimeLabel() }}</strong></span><span>状态 <strong>{{ task.status }}</strong></span>
<button v-if="mode === 'dialog'" class="tasktree-detail-dock-button" type="button" title="吸附到右侧" aria-label="吸附到右侧" @click="emit('mode', 'docked')"><PanelRight :size="15" aria-hidden="true" /></button>
</footer>
</div>
@@ -463,6 +463,7 @@ function syncSelectedTaskFromRoute() {
<span v-else class="tasktree-disclosure-spacer" aria-hidden="true" />
<div class="tasktree-title" role="button" tabindex="0" @click="selectTaskFromTitle($event, root)" @keydown.enter="selectTask(root)">
<span class="tasktree-status" :data-status="root.status" />
<span class="tasktree-outline-ref">{{ root.outlineRef }}</span>
<MessageMarkdown class="tasktree-title-markdown" :source="root.title" />
</div>
</div>
@@ -490,6 +491,7 @@ function syncSelectedTaskFromRoute() {
<span v-else class="tasktree-disclosure-spacer" aria-hidden="true" />
<div class="tasktree-title" role="button" tabindex="0" @click="selectTaskFromTitle($event, task)" @keydown.enter="selectTask(task)">
<span class="tasktree-status" :data-status="task.status" />
<span class="tasktree-outline-ref">{{ task.outlineRef }}</span>
<MessageMarkdown class="tasktree-title-markdown" :source="task.title" />
</div>
</div>
@@ -617,6 +619,7 @@ function syncSelectedTaskFromRoute() {
.tasktree-label { height: 46px; border-bottom: 1px solid #edf0ef; background: #fff; padding: 0 8px; display: flex; align-items: center; color: var(--text-primary); min-width: 0; }
.tasktree-task-group > .tasktree-label { background: #f7f9f8; font-weight: 600; }
.tasktree-label:hover { background: #f2f7f5; }
.tasktree-outline-ref { flex: 0 0 auto; color: #587078; font-family: var(--console-font-mono); font-size: 11px; font-weight: 700; }
.tasktree-label.subtask { padding-left: 30px; color: var(--text-secondary); }
.tasktree-label.subsubtask { padding-left: 54px; color: var(--text-secondary); }
.tasktree-group-label { height: 68px; padding: 9px 14px; display: grid; align-content: center; gap: 5px; cursor: pointer; }