feat(frontend): add four-quadrant kanban task management system

- Add KanbanPanel component with four-quadrant task layout
- Add KanbanDetail component for task configuration modal
- Add "待办" (Todo) module to sidebar collapsed icon rail
- Click TODAY'S STATUS card or sidebar icon to open kanban drawer
- Click quadrant check icon to open detail modal with Teleport to body
- Apply blur effect to sidebar and chat area when detail modal is open
- Import ListTodo icon from lucide-vue-next
- Update sidebar labels to English for consistency
This commit is contained in:
2026-04-06 23:48:52 +08:00
parent 3cf8762b96
commit 3bff9b3b93
5 changed files with 954 additions and 17 deletions

View File

@@ -15,6 +15,8 @@ import KnowledgeHudPanel from '@/components/chat/KnowledgeHudPanel.vue'
import KnowledgeSlidePanel from '@/components/chat/KnowledgeSlidePanel.vue'
import KnowledgeHUDPreview from '@/components/chat/KnowledgeHUDPreview.vue'
import OrchestrationPanel from '@/components/chat/OrchestrationPanel.vue'
import KanbanPanel from '@/components/chat/KanbanPanel.vue'
import KanbanDetail from '@/components/chat/KanbanDetail.vue'
import TelemetrySparkline from '@/components/chat/TelemetrySparkline.vue'
import NavShortcutRow from '@/components/navigation/NavShortcutRow.vue'
import DailyDigestCard from '@/components/memory/DailyDigestCard.vue'
@@ -78,12 +80,28 @@ const {
// --- Local UI state ---
const sidebarCollapsed = ref(false)
const orchestrationDrawerOpen = ref(false)
const kanbanDrawerOpen = ref(false)
const kanbanDetailOpen = ref(false)
const kanbanDetailQuadrant = ref<{ id: string; title: string; color: string } | null>(null)
const knowledgeHudOpen = ref(false)
const selectedFolder = ref<any>(null)
const previewDoc = ref<any>(null)
function openOrchestrationDrawer() { orchestrationDrawerOpen.value = true }
function closeOrchestrationDrawer() { orchestrationDrawerOpen.value = false }
function openKanbanDrawer() { kanbanDrawerOpen.value = true }
function closeKanbanDrawer() { kanbanDrawerOpen.value = false }
function openKanbanDetail(quadrantId: string) {
const quadrantMap: Record<string, { title: string; color: string }> = {
'urgent-important': { title: '重要且紧急', color: '#f56565' },
'not-urgent-important': { title: '重要不紧急', color: '#ecc94b' },
'urgent-not-important': { title: '紧急不重要', color: '#42b9f5' },
'not-urgent-not-important': { title: '不重要不紧急', color: '#97c950' },
}
kanbanDetailQuadrant.value = { id: quadrantId, ...quadrantMap[quadrantId] }
kanbanDetailOpen.value = true
}
function closeKanbanDetail() { kanbanDetailOpen.value = false }
function openKnowledgeHud() {
selectedFolder.value = null
previewDoc.value = null
@@ -208,7 +226,7 @@ function renderMarkdown(content: string) {
</script>
<template>
<div class="chat-view">
<div class="chat-view" :class="{ 'blur-when-modal': kanbanDetailOpen }">
<!-- Conversation list sidebar -->
<aside class="conv-sidebar jarvis-sidebar" :class="{ collapsed: sidebarCollapsed }">
<div v-if="sidebarCollapsed" class="jarvis-sidebar-icon-rail">
@@ -219,17 +237,18 @@ function renderMarkdown(content: string) {
type="button"
:title="module.label"
:aria-label="module.label"
@click="sidebarCollapsed = false"
@click="module.id === 'kanban' ? openKanbanDrawer() : (sidebarCollapsed = false)"
>
<component :is="module.icon" :size="18" />
</button>
</div>
<div v-else class="jarvis-sidebar-scroll">
<div class="section-label">// DAILY STATUS</div>
<div class="jarvis-panel jarvis-date-panel">
<div class="jarvis-date-row">
<div class="jarvis-date-meta">
<div class="jarvis-month">{{ clientTime.toLocaleString('zh-CN', { month: 'long' }) }} {{ clientTime.getFullYear() }}</div>
<div class="jarvis-month">{{ clientTime.toLocaleString('en-US', { month: 'long', year: 'numeric' }) }}</div>
<div class="jarvis-time">{{ clientTime.toLocaleTimeString('en-US', { hour12: true }) }}</div>
</div>
<div class="jarvis-location">
@@ -259,13 +278,13 @@ function renderMarkdown(content: string) {
</div>
<div class="jarvis-panel jarvis-plan-panel">
<div class="jarvis-section-title">&#x4ECA;&#x65E5;&#x8BA1;&#x5212;&#x60C5;&#x51B5;</div>
<div class="jarvis-panel jarvis-plan-panel" @click="openKanbanDrawer" style="cursor: pointer;">
<div class="jarvis-section-title">TODAY'S STATUS</div>
<div class="jarvis-status-shell">
<div class="jarvis-progress-ring" :style="{ '--completion': `${todayPlanCounters.completion}%` }">
<div class="jarvis-progress-core">
<strong>{{ todayPlanCounters.completion }}%</strong>
<span>&#x5B8C;&#x6210;&#x7387;</span>
<span>COMPLETION</span>
</div>
</div>
@@ -285,7 +304,7 @@ function renderMarkdown(content: string) {
</div>
<div class="jarvis-panel jarvis-focus-panel">
<div class="jarvis-section-title">&#x4ECA;&#x65E5;&#x8BA1;&#x5212;&#x91CD;&#x70B9;</div>
<div class="jarvis-section-title">TODAY'S FOCUS</div>
<ul v-if="sidebarFocusItems.length > 0" class="jarvis-focus-list">
<li v-for="(item, index) in sidebarFocusItems" :key="item.id" class="jarvis-focus-item" :class="`is-${item.tone}`">
<span class="focus-order" :class="{ 'is-done': item.tone === 'done' }">
@@ -303,15 +322,15 @@ function renderMarkdown(content: string) {
</div>
<div class="jarvis-panel jarvis-review-panel">
<div class="jarvis-section-title">&#x672C;&#x6708;&#x8BA1;&#x5212;&#x590D;&#x76D8;</div>
<div class="jarvis-section-title">MONTHLY REVIEW</div>
<div class="jarvis-review-group">
<div class="jarvis-review-subtitle">&#x6210;&#x679C;</div>
<div class="jarvis-review-subtitle">ACHIEVEMENTS</div>
<ul class="jarvis-review-list">
<li v-for="item in sidebarReviewAchievements" :key="item" class="jarvis-review-item">{{ item }}</li>
</ul>
</div>
<div class="jarvis-review-group">
<div class="jarvis-review-subtitle">&#x53CD;&#x601D;</div>
<div class="jarvis-review-subtitle">REFLECTIONS</div>
<ul class="jarvis-review-list reflection">
<li v-for="item in sidebarReviewReflections" :key="item" class="jarvis-review-item">{{ item }}</li>
</ul>
@@ -730,6 +749,36 @@ function renderMarkdown(content: string) {
</aside>
</div>
<!-- Kanban Drawer (四象限任务管理) -->
<div class="kanban-drawer-shell" :class="{ open: kanbanDrawerOpen }">
<button
v-if="kanbanDrawerOpen"
class="kanban-drawer-backdrop"
type="button"
aria-label="Close kanban"
@click="closeKanbanDrawer"
></button>
<aside class="kanban-drawer" :class="{ open: kanbanDrawerOpen }">
<KanbanPanel
:visible="kanbanDrawerOpen"
@close="closeKanbanDrawer"
@open-detail="openKanbanDetail"
/>
</aside>
</div>
<!-- Kanban Detail Modal (Teleported to body to avoid blur) -->
<Teleport to="body">
<KanbanDetail
v-if="kanbanDetailQuadrant"
:visible="kanbanDetailOpen"
:quadrant-id="kanbanDetailQuadrant.id"
:quadrant-title="kanbanDetailQuadrant.title"
:quadrant-color="kanbanDetailQuadrant.color"
@close="closeKanbanDetail"
/>
</Teleport>
<!-- Knowledge Side Panel (Phase 02) -->
<Transition name="slide">
<KnowledgeSlidePanel