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:
496
frontend/src/components/chat/KanbanDetail.vue
Normal file
496
frontend/src/components/chat/KanbanDetail.vue
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
completed: boolean
|
||||||
|
description?: string
|
||||||
|
assignee?: string
|
||||||
|
priority?: string
|
||||||
|
createdAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
quadrantId: string
|
||||||
|
quadrantTitle: string
|
||||||
|
quadrantColor: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const activeTab = ref<'comments' | 'history'>('comments')
|
||||||
|
const commentInput = ref('')
|
||||||
|
|
||||||
|
const mockTask: Task = {
|
||||||
|
id: '1',
|
||||||
|
title: '将2.0的服务部署到83服务器上',
|
||||||
|
completed: true,
|
||||||
|
description: '',
|
||||||
|
assignee: 'caoxiaozhu',
|
||||||
|
priority: '重要且紧急 (P1)',
|
||||||
|
createdAt: '2024-03-21 14:49:35',
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate() {
|
||||||
|
return new Date().toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="kanban-detail-overlay" :class="{ visible }" @click.self="emit('close')">
|
||||||
|
<div class="kanban-detail-panel">
|
||||||
|
<button class="detail-close" type="button" aria-label="Close detail" @click="emit('close')">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="detail-header">
|
||||||
|
<div class="task-title-section">
|
||||||
|
<div class="task-title">
|
||||||
|
<span class="check-icon">☑</span>
|
||||||
|
{{ mockTask.title }}
|
||||||
|
</div>
|
||||||
|
<div class="task-meta">
|
||||||
|
<span class="avatar">C</span>
|
||||||
|
{{ mockTask.assignee }} 创建于:{{ mockTask.createdAt }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-badge" :style="{ background: quadrantColor + '20', color: quadrantColor }">
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-content">
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">
|
||||||
|
<span class="icon">☞</span>
|
||||||
|
描述
|
||||||
|
</div>
|
||||||
|
<div class="description-box">
|
||||||
|
添加详细描述...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-info-list">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="icon">☰</span>
|
||||||
|
负责人:<span class="avatar">C</span>{{ mockTask.assignname }}
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="icon">⚑</span>
|
||||||
|
优先级:<span class="priority-high">{{ mockTask.priority }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="icon">📅</span>
|
||||||
|
任务状态:<span :class="{ 'status-completed': mockTask.completed }">
|
||||||
|
{{ mockTask.completed ? '已完成' : '进行中' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title subtask-section">
|
||||||
|
<span>
|
||||||
|
<span class="icon">☑</span>
|
||||||
|
子任务
|
||||||
|
</span>
|
||||||
|
<button class="btn-add-subtask" type="button">添加子任务</button>
|
||||||
|
</div>
|
||||||
|
<div class="subtask-empty">暂无子任务</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">
|
||||||
|
<span class="icon">💬</span>
|
||||||
|
<span class="tab" :class="{ active: activeTab === 'comments' }" @click="activeTab = 'comments'">评论</span>
|
||||||
|
<span class="tab" :class="{ active: activeTab === 'history' }" @click="activeTab = 'history'">操作记录</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-empty">没有相关内容</div>
|
||||||
|
<div class="comment-input-area">
|
||||||
|
<textarea
|
||||||
|
v-model="commentInput"
|
||||||
|
class="comment-input"
|
||||||
|
placeholder="输入评论,Enter发表评论,Shift+Enter换行"
|
||||||
|
></textarea>
|
||||||
|
<button class="btn-comment" type="button">评论</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar">
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">✂</span>
|
||||||
|
标记未完成
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">↻</span>
|
||||||
|
优先级
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">👤</span>
|
||||||
|
负责人
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">📆</span>
|
||||||
|
计划时间
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">📎</span>
|
||||||
|
添加附件
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">📧</span>
|
||||||
|
关注人
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn" type="button">
|
||||||
|
<span class="icon">📁</span>
|
||||||
|
归档
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-btn btn-delete" type="button">
|
||||||
|
<span class="icon">🗑</span>
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.kanban-detail-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(2, 6, 14, 0.85);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity var(--transition-mid), visibility var(--transition-mid);
|
||||||
|
z-index: 9999;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-detail-overlay.visible {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-detail-panel {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1100px;
|
||||||
|
max-height: 90%;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(34, 211, 238, 0.22);
|
||||||
|
background: linear-gradient(180deg, rgba(12, 20, 36, 0.98), rgba(8, 14, 28, 0.96));
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 60px rgba(34, 211, 238, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: scale(0.95);
|
||||||
|
transition: transform var(--transition-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-detail-overlay.visible .kanban-detail-panel {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 20px;
|
||||||
|
font-size: 28px;
|
||||||
|
color: #ff4d4f;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-close:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 24px;
|
||||||
|
border-bottom: 1px solid rgba(0, 243, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title-section {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e0f7ff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #00f3ff, #0891b2);
|
||||||
|
color: #000;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 240px;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e0f7ff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title .icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title .tab {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
cursor: pointer;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-right: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title .tab.active {
|
||||||
|
color: #e0f7ff;
|
||||||
|
border-bottom-color: #00f3ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-box {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid rgba(0, 243, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 60px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #e0f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .icon {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 14px;
|
||||||
|
width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-high {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-completed {
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtask-section {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-subtask {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid rgba(0, 243, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(0, 243, 255, 0.06);
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-subtask:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.12);
|
||||||
|
border-color: rgba(0, 243, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtask-empty {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-empty {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-area {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: flex-end;
|
||||||
|
border-top: 1px solid rgba(0, 243, 255, 0.1);
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid rgba(0, 243, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
color: #e0f7ff;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input:focus {
|
||||||
|
border-color: rgba(0, 243, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input::placeholder {
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-comment {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid rgba(0, 243, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-comment:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-btn {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid rgba(0, 243, 255, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
color: #e0f7ff;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-btn:hover {
|
||||||
|
border-color: rgba(0, 243, 255, 0.4);
|
||||||
|
background: rgba(0, 243, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-btn .icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-btn.btn-delete {
|
||||||
|
border-color: rgba(255, 77, 79, 0.3);
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-btn.btn-delete:hover {
|
||||||
|
background: rgba(255, 77, 79, 0.1);
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
.detail-content::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
336
frontend/src/components/chat/KanbanPanel.vue
Normal file
336
frontend/src/components/chat/KanbanPanel.vue
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface KanbanTask {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KanbanQuadrant {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
color: string
|
||||||
|
tasks: KanbanTask[]
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
toggleTask: [taskId: string]
|
||||||
|
openDetail: [quadrantId: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const quadrants = computed<KanbanQuadrant[]>(() => [
|
||||||
|
{
|
||||||
|
id: 'urgent-important',
|
||||||
|
title: '重要且紧急',
|
||||||
|
color: '#f56565',
|
||||||
|
tasks: [
|
||||||
|
{ id: '1', title: '将2.0的服务部署到83服务器上', completed: true },
|
||||||
|
{ id: '2', title: '将83上的遗留的ygzy, recommand和langchain部署起来', completed: true },
|
||||||
|
{ id: '3', title: '本地知识库接口开发', completed: true },
|
||||||
|
{ id: '4', title: '将"远光智言"中加入本地文档的问答功能', completed: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'not-urgent-important',
|
||||||
|
title: '重要不紧急',
|
||||||
|
color: '#ecc94b',
|
||||||
|
tasks: [
|
||||||
|
{ id: '5', title: '关于X Request的集成', completed: false },
|
||||||
|
{ id: '6', title: '调研GLM Code', completed: false },
|
||||||
|
{ id: '7', title: '页面集成llamafactory功能进行开发', completed: false },
|
||||||
|
{ id: '8', title: '预训练功能开发', completed: false },
|
||||||
|
{ id: '9', title: '数据生成相关代码', completed: true },
|
||||||
|
{ id: '10', title: '调研Transformer的一些基础原理', completed: true },
|
||||||
|
{ id: '11', title: '英文分词并且找寻关键词', completed: false },
|
||||||
|
{ id: '12', title: '适配多个数据库类型', completed: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'urgent-not-important',
|
||||||
|
title: '紧急不重要',
|
||||||
|
color: '#42b9f5',
|
||||||
|
tasks: [
|
||||||
|
{ id: '13', title: '文件预处理功能', completed: false },
|
||||||
|
{ id: '14', title: '机器学习 - 西瓜书', completed: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'not-urgent-not-important',
|
||||||
|
title: '不重要不紧急',
|
||||||
|
color: '#97c950',
|
||||||
|
tasks: [],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="kanban-panel" :class="{ visible }">
|
||||||
|
<div class="kanban-frame">
|
||||||
|
<div class="kanban-header">
|
||||||
|
<div class="kanban-title-row">
|
||||||
|
<span class="kanban-cloud">☁</span>
|
||||||
|
<span class="kanban-title">我的待办</span>
|
||||||
|
</div>
|
||||||
|
<div class="kanban-nav">
|
||||||
|
<a href="#" class="nav-link">🗓 待办日程</a>
|
||||||
|
<a href="#" class="nav-link">🗹 已完成的任务</a>
|
||||||
|
<a href="#" class="nav-link">♡ 我关注的任务</a>
|
||||||
|
<a href="#" class="nav-link">✉ 周报/日报</a>
|
||||||
|
</div>
|
||||||
|
<button class="kanban-close" type="button" aria-label="Close kanban" @click="emit('close')">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kanban-quadrants">
|
||||||
|
<div
|
||||||
|
v-for="quadrant in quadrants"
|
||||||
|
:key="quadrant.id"
|
||||||
|
class="kanban-quadrant"
|
||||||
|
:style="{ '--quadrant-color': quadrant.color }"
|
||||||
|
>
|
||||||
|
<div class="quadrant-header">
|
||||||
|
<span class="quadrant-title">☛ {{ quadrant.title }}</span>
|
||||||
|
<button class="quadrant-check" type="button" @click="emit('openDetail', quadrant.id)">☑</button>
|
||||||
|
</div>
|
||||||
|
<div class="quadrant-content">
|
||||||
|
<template v-if="quadrant.tasks.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="task in quadrant.tasks"
|
||||||
|
:key="task.id"
|
||||||
|
class="task-item"
|
||||||
|
:class="{ completed: task.completed }"
|
||||||
|
@click="emit('toggleTask', task.id)"
|
||||||
|
>
|
||||||
|
<input type="checkbox" :checked="task.completed" disabled />
|
||||||
|
<span class="task-title">{{ task.title }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="empty-state">
|
||||||
|
恭喜你!已完成了所有待办
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.kanban-panel {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 12px 12px 12px 0;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity var(--transition-mid), visibility var(--transition-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-panel.visible {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-frame {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid rgba(34, 211, 238, 0.18);
|
||||||
|
background: linear-gradient(180deg, rgba(8, 14, 28, 0.96), rgba(6, 10, 20, 0.92));
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 0 20px rgba(34, 211, 238, 0.08);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid rgba(0, 243, 255, 0.1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-cloud {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e0f7ff;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-nav .nav-link {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-nav .nav-link:hover {
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-close {
|
||||||
|
margin-left: 12px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||||
|
background: rgba(8, 14, 26, 0.86);
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-close:hover {
|
||||||
|
border-color: rgba(34, 211, 238, 0.4);
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
background: rgba(8, 20, 36, 0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-quadrants {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-quadrant {
|
||||||
|
background: rgba(0, 20, 40, 0.4);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-header {
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 11px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--quadrant-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-title {
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-check {
|
||||||
|
opacity: 0.8;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-check:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #e0f7ff;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item:hover {
|
||||||
|
background-color: rgba(0, 243, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item input[type="checkbox"] {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.completed {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: rgba(224, 247, 255, 0.5);
|
||||||
|
background-color: rgba(0, 243, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
color: rgba(224, 247, 255, 0.4);
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
.quadrant-content::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-content::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quadrant-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,12 @@
|
|||||||
overflow: visible;
|
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 {
|
.sidebar-runtime-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
@@ -444,6 +450,50 @@
|
|||||||
pointer-events: auto;
|
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 {
|
.knowledge-hud-backdrop {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -610,6 +660,11 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jarvis-sidebar-scroll .section-label {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.conv-sidebar-header .section-label {
|
.conv-sidebar-header .section-label {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -2345,8 +2400,8 @@
|
|||||||
|
|
||||||
.jarvis-progress-core span {
|
.jarvis-progress-core span {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 9px;
|
font-size: 7px;
|
||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.1em;
|
||||||
color: rgba(155, 231, 255, 0.54);
|
color: rgba(155, 231, 255, 0.54);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { computed, onMounted, ref, watch } from 'vue'
|
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'
|
import { scheduleCenterApi, type ScheduleCenterDateResponse, type ScheduleCenterDaySummary } from '@/api/scheduleCenter'
|
||||||
|
|
||||||
export interface SidebarFocusItem {
|
export interface SidebarFocusItem {
|
||||||
@@ -13,6 +13,7 @@ export const sidebarCollapsedModules = [
|
|||||||
{ id: 'calendar', label: '日历', icon: Sun },
|
{ id: 'calendar', label: '日历', icon: Sun },
|
||||||
{ id: 'status', label: '计划', icon: Database },
|
{ id: 'status', label: '计划', icon: Database },
|
||||||
{ id: 'focus', label: '重点', icon: Sparkles },
|
{ id: 'focus', label: '重点', icon: Sparkles },
|
||||||
|
{ id: 'kanban', label: '待办', icon: ListTodo },
|
||||||
{ id: 'review', label: '复盘', icon: CornerDownLeft },
|
{ id: 'review', label: '复盘', icon: CornerDownLeft },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -111,10 +112,10 @@ export function useSidebarPlan(clientTimeRef: { value: Date }, loadDailyDigestFn
|
|||||||
))
|
))
|
||||||
|
|
||||||
const sidebarStatusBreakdown = computed(() => [
|
const sidebarStatusBreakdown = computed(() => [
|
||||||
{ key: 'done', label: '已完成', value: todayPlanCounters.value.done, tone: 'done' },
|
{ key: 'done', label: 'Completed', value: todayPlanCounters.value.done, tone: 'done' },
|
||||||
{ key: 'doing', label: '进行中', value: todayPlanCounters.value.doing, tone: 'doing' },
|
{ key: 'doing', label: 'In Progress', value: todayPlanCounters.value.doing, tone: 'doing' },
|
||||||
{ key: 'pending', label: '未开始', value: todayPlanCounters.value.pending, tone: 'pending' },
|
{ key: 'pending', label: 'Pending', value: todayPlanCounters.value.pending, tone: 'pending' },
|
||||||
{ key: 'total', label: '今日合计', value: todayPlanCounters.value.total, tone: 'total' },
|
{ 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 KnowledgeSlidePanel from '@/components/chat/KnowledgeSlidePanel.vue'
|
||||||
import KnowledgeHUDPreview from '@/components/chat/KnowledgeHUDPreview.vue'
|
import KnowledgeHUDPreview from '@/components/chat/KnowledgeHUDPreview.vue'
|
||||||
import OrchestrationPanel from '@/components/chat/OrchestrationPanel.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 TelemetrySparkline from '@/components/chat/TelemetrySparkline.vue'
|
||||||
import NavShortcutRow from '@/components/navigation/NavShortcutRow.vue'
|
import NavShortcutRow from '@/components/navigation/NavShortcutRow.vue'
|
||||||
import DailyDigestCard from '@/components/memory/DailyDigestCard.vue'
|
import DailyDigestCard from '@/components/memory/DailyDigestCard.vue'
|
||||||
@@ -78,12 +80,28 @@ const {
|
|||||||
// --- Local UI state ---
|
// --- Local UI state ---
|
||||||
const sidebarCollapsed = ref(false)
|
const sidebarCollapsed = ref(false)
|
||||||
const orchestrationDrawerOpen = 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 knowledgeHudOpen = ref(false)
|
||||||
const selectedFolder = ref<any>(null)
|
const selectedFolder = ref<any>(null)
|
||||||
const previewDoc = ref<any>(null)
|
const previewDoc = ref<any>(null)
|
||||||
|
|
||||||
function openOrchestrationDrawer() { orchestrationDrawerOpen.value = true }
|
function openOrchestrationDrawer() { orchestrationDrawerOpen.value = true }
|
||||||
function closeOrchestrationDrawer() { orchestrationDrawerOpen.value = false }
|
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() {
|
function openKnowledgeHud() {
|
||||||
selectedFolder.value = null
|
selectedFolder.value = null
|
||||||
previewDoc.value = null
|
previewDoc.value = null
|
||||||
@@ -208,7 +226,7 @@ function renderMarkdown(content: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="chat-view">
|
<div class="chat-view" :class="{ 'blur-when-modal': kanbanDetailOpen }">
|
||||||
<!-- Conversation list sidebar -->
|
<!-- Conversation list sidebar -->
|
||||||
<aside class="conv-sidebar jarvis-sidebar" :class="{ collapsed: sidebarCollapsed }">
|
<aside class="conv-sidebar jarvis-sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||||
<div v-if="sidebarCollapsed" class="jarvis-sidebar-icon-rail">
|
<div v-if="sidebarCollapsed" class="jarvis-sidebar-icon-rail">
|
||||||
@@ -219,17 +237,18 @@ function renderMarkdown(content: string) {
|
|||||||
type="button"
|
type="button"
|
||||||
:title="module.label"
|
:title="module.label"
|
||||||
:aria-label="module.label"
|
:aria-label="module.label"
|
||||||
@click="sidebarCollapsed = false"
|
@click="module.id === 'kanban' ? openKanbanDrawer() : (sidebarCollapsed = false)"
|
||||||
>
|
>
|
||||||
<component :is="module.icon" :size="18" />
|
<component :is="module.icon" :size="18" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="jarvis-sidebar-scroll">
|
<div v-else class="jarvis-sidebar-scroll">
|
||||||
|
<div class="section-label">// DAILY STATUS</div>
|
||||||
<div class="jarvis-panel jarvis-date-panel">
|
<div class="jarvis-panel jarvis-date-panel">
|
||||||
<div class="jarvis-date-row">
|
<div class="jarvis-date-row">
|
||||||
<div class="jarvis-date-meta">
|
<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 class="jarvis-time">{{ clientTime.toLocaleTimeString('en-US', { hour12: true }) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="jarvis-location">
|
<div class="jarvis-location">
|
||||||
@@ -259,13 +278,13 @@ function renderMarkdown(content: string) {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="jarvis-panel jarvis-plan-panel">
|
<div class="jarvis-panel jarvis-plan-panel" @click="openKanbanDrawer" style="cursor: pointer;">
|
||||||
<div class="jarvis-section-title">今日计划情况</div>
|
<div class="jarvis-section-title">TODAY'S STATUS</div>
|
||||||
<div class="jarvis-status-shell">
|
<div class="jarvis-status-shell">
|
||||||
<div class="jarvis-progress-ring" :style="{ '--completion': `${todayPlanCounters.completion}%` }">
|
<div class="jarvis-progress-ring" :style="{ '--completion': `${todayPlanCounters.completion}%` }">
|
||||||
<div class="jarvis-progress-core">
|
<div class="jarvis-progress-core">
|
||||||
<strong>{{ todayPlanCounters.completion }}%</strong>
|
<strong>{{ todayPlanCounters.completion }}%</strong>
|
||||||
<span>完成率</span>
|
<span>COMPLETION</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -285,7 +304,7 @@ function renderMarkdown(content: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="jarvis-panel jarvis-focus-panel">
|
<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">
|
<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}`">
|
<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' }">
|
<span class="focus-order" :class="{ 'is-done': item.tone === 'done' }">
|
||||||
@@ -303,15 +322,15 @@ function renderMarkdown(content: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="jarvis-panel jarvis-review-panel">
|
<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-group">
|
||||||
<div class="jarvis-review-subtitle">成果</div>
|
<div class="jarvis-review-subtitle">ACHIEVEMENTS</div>
|
||||||
<ul class="jarvis-review-list">
|
<ul class="jarvis-review-list">
|
||||||
<li v-for="item in sidebarReviewAchievements" :key="item" class="jarvis-review-item">{{ item }}</li>
|
<li v-for="item in sidebarReviewAchievements" :key="item" class="jarvis-review-item">{{ item }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="jarvis-review-group">
|
<div class="jarvis-review-group">
|
||||||
<div class="jarvis-review-subtitle">反思</div>
|
<div class="jarvis-review-subtitle">REFLECTIONS</div>
|
||||||
<ul class="jarvis-review-list reflection">
|
<ul class="jarvis-review-list reflection">
|
||||||
<li v-for="item in sidebarReviewReflections" :key="item" class="jarvis-review-item">{{ item }}</li>
|
<li v-for="item in sidebarReviewReflections" :key="item" class="jarvis-review-item">{{ item }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -730,6 +749,36 @@ function renderMarkdown(content: string) {
|
|||||||
</aside>
|
</aside>
|
||||||
</div>
|
</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) -->
|
<!-- Knowledge Side Panel (Phase 02) -->
|
||||||
<Transition name="slide">
|
<Transition name="slide">
|
||||||
<KnowledgeSlidePanel
|
<KnowledgeSlidePanel
|
||||||
|
|||||||
Reference in New Issue
Block a user