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:
@@ -14,6 +14,12 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.chat-view.blur-when-modal .conv-sidebar,
|
||||
.chat-view.blur-when-modal .chat-area {
|
||||
filter: blur(8px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sidebar-runtime-panel {
|
||||
flex: 1;
|
||||
padding: 12px 14px;
|
||||
@@ -444,6 +450,50 @@
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Kanban Drawer (四象限任务管理) */
|
||||
.kanban-drawer-shell {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 32;
|
||||
}
|
||||
|
||||
.kanban-drawer-shell.open {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.kanban-drawer-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: none;
|
||||
background: rgba(2, 6, 14, 0.52);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.kanban-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--transition-mid);
|
||||
}
|
||||
|
||||
.kanban-drawer.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.kanban-drawer :deep(.kanban-panel) {
|
||||
height: 100%;
|
||||
padding: 12px 12px 12px 0;
|
||||
}
|
||||
|
||||
.kanban-drawer :deep(.kanban-frame) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.knowledge-hud-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -610,6 +660,11 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.jarvis-sidebar-scroll .section-label {
|
||||
margin-bottom: 8px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.conv-sidebar-header .section-label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -2345,8 +2400,8 @@
|
||||
|
||||
.jarvis-progress-core span {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 7px;
|
||||
letter-spacing: 0.1em;
|
||||
color: rgba(155, 231, 255, 0.54);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { CornerDownLeft, Database, Sparkles, Sun } from 'lucide-vue-next'
|
||||
import { CornerDownLeft, Database, Sparkles, Sun, ListTodo } from 'lucide-vue-next'
|
||||
import { scheduleCenterApi, type ScheduleCenterDateResponse, type ScheduleCenterDaySummary } from '@/api/scheduleCenter'
|
||||
|
||||
export interface SidebarFocusItem {
|
||||
@@ -13,6 +13,7 @@ export const sidebarCollapsedModules = [
|
||||
{ id: 'calendar', label: '日历', icon: Sun },
|
||||
{ id: 'status', label: '计划', icon: Database },
|
||||
{ id: 'focus', label: '重点', icon: Sparkles },
|
||||
{ id: 'kanban', label: '待办', icon: ListTodo },
|
||||
{ id: 'review', label: '复盘', icon: CornerDownLeft },
|
||||
]
|
||||
|
||||
@@ -111,10 +112,10 @@ export function useSidebarPlan(clientTimeRef: { value: Date }, loadDailyDigestFn
|
||||
))
|
||||
|
||||
const sidebarStatusBreakdown = computed(() => [
|
||||
{ key: 'done', label: '已完成', value: todayPlanCounters.value.done, tone: 'done' },
|
||||
{ key: 'doing', label: '进行中', value: todayPlanCounters.value.doing, tone: 'doing' },
|
||||
{ key: 'pending', label: '未开始', value: todayPlanCounters.value.pending, tone: 'pending' },
|
||||
{ key: 'total', label: '今日合计', value: todayPlanCounters.value.total, tone: 'total' },
|
||||
{ key: 'done', label: 'Completed', value: todayPlanCounters.value.done, tone: 'done' },
|
||||
{ key: 'doing', label: 'In Progress', value: todayPlanCounters.value.doing, tone: 'doing' },
|
||||
{ key: 'pending', label: 'Pending', value: todayPlanCounters.value.pending, tone: 'pending' },
|
||||
{ key: 'total', label: 'Total', value: todayPlanCounters.value.total, tone: 'total' },
|
||||
])
|
||||
|
||||
// 模拟数据 - 用于测试滑动条
|
||||
|
||||
@@ -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">今日计划情况</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>完成率</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">今日计划重点</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">本月计划复盘</div>
|
||||
<div class="jarvis-section-title">MONTHLY REVIEW</div>
|
||||
<div class="jarvis-review-group">
|
||||
<div class="jarvis-review-subtitle">成果</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">反思</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
|
||||
|
||||
Reference in New Issue
Block a user