diff --git a/web/src/views/Plan.vue b/web/src/views/Plan.vue
index 5bd2eef..10ce958 100644
--- a/web/src/views/Plan.vue
+++ b/web/src/views/Plan.vue
@@ -1,70 +1,13 @@
@@ -170,99 +113,3 @@ const getTaskCount = (status: string) => {
-
-
diff --git a/web/src/views/plan/plan.css b/web/src/views/plan/plan.css
new file mode 100644
index 0000000..c1ecc16
--- /dev/null
+++ b/web/src/views/plan/plan.css
@@ -0,0 +1,92 @@
+.plan-page {
+ background-color: #0f1419;
+}
+
+.search-input {
+ background-color: #1f2937;
+ border: 1px solid #374151;
+ border-radius: 8px;
+ padding: 10px 12px 10px 36px;
+ color: white;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.2s;
+}
+
+.search-input:focus {
+ border-color: #f97316;
+}
+
+.search-input::placeholder {
+ color: #6b7280;
+}
+
+.table-row {
+ border-top: 1px solid #2a2a3a;
+ transition: background-color 0.2s;
+}
+
+.table-row:hover {
+ background-color: rgba(255, 255, 255, 0.02);
+}
+
+.task-tag {
+ background-color: #374151;
+ color: #d1d5db;
+ font-size: 11px;
+ padding: 2px 6px;
+ border-radius: 4px;
+}
+
+.ml-13 {
+ margin-left: 3.25rem;
+}
+
+.btn-icon {
+ padding: 6px;
+ border-radius: 6px;
+ transition: all 0.2s;
+}
+
+.btn-icon:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.empty-box {
+ min-height: 300px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.empty-icon {
+ width: 80px;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #1f2937, #111827);
+ border-radius: 20px;
+ margin-bottom: 16px;
+ color: #6b7280;
+}
+
+.empty-text {
+ font-size: 16px;
+ font-weight: 500;
+ color: white;
+ margin-bottom: 4px;
+}
+
+.empty-tip {
+ font-size: 14px;
+ color: #6b7280;
+}
+
+.line-clamp-2 {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
diff --git a/web/src/views/plan/usePlan.ts b/web/src/views/plan/usePlan.ts
new file mode 100644
index 0000000..fba9fc3
--- /dev/null
+++ b/web/src/views/plan/usePlan.ts
@@ -0,0 +1,87 @@
+import { ref, computed } from 'vue'
+
+export interface PlanTask {
+ id: number
+ name: string
+ status: 'running' | 'stopped'
+ triggerType: string
+ nextRun: string
+ lastRun: string
+ notifyChannel: string
+ executionCount: number
+ description: string
+ tags: string[]
+}
+
+export function usePlan() {
+ const tasks = ref([
+ {
+ id: 1,
+ name: 'Human-like Heartbeat',
+ status: 'running',
+ triggerType: 'Interval 30 minutes',
+ nextRun: '2026/03/10 16:26',
+ lastRun: '2026/03/10 15:56',
+ notifyChannel: '-',
+ executionCount: 13,
+ description: 'Check if proactive messages need to be sent (greetings/reminders/follow-ups)',
+ tags: ['System Task', 'Agent Task', 'Interval']
+ },
+ {
+ id: 2,
+ name: 'Memory Organization',
+ status: 'running',
+ triggerType: 'Interval 3 hours',
+ nextRun: '2026/03/10 18:35',
+ lastRun: '2026/03/10 15:35',
+ notifyChannel: '-',
+ executionCount: 2,
+ description: 'Execute memory organization: organize chat history, extract key memories, refresh MEMORY.md',
+ tags: ['System Task', 'Agent Task', 'Interval']
+ },
+ {
+ id: 3,
+ name: 'System Self-Check',
+ status: 'running',
+ triggerType: 'Daily 04:00',
+ nextRun: '2026/03/11 04:00',
+ lastRun: 'Never',
+ notifyChannel: '-',
+ executionCount: 0,
+ description: 'Execute system self-check: analyze ERROR logs, try to fix tool issues, generate report',
+ tags: ['System Task', 'Agent Task', 'Daily']
+ },
+ ])
+
+ const filterStatus = ref('all')
+ const searchQuery = ref('')
+
+ const filteredTasks = computed(() => {
+ let result = tasks.value
+ if (filterStatus.value !== 'all') {
+ result = result.filter(t => t.status === filterStatus.value)
+ }
+ if (searchQuery.value) {
+ const query = searchQuery.value.toLowerCase()
+ result = result.filter(t =>
+ t.name.toLowerCase().includes(query) ||
+ t.description.toLowerCase().includes(query)
+ )
+ }
+ return result
+ })
+
+ const getTaskCount = (status: string) => {
+ if (status === 'running') return tasks.value.filter(t => t.status === 'running').length
+ if (status === 'stopped') return tasks.value.filter(t => t.status === 'stopped').length
+ return tasks.value.length
+ }
+
+ return {
+ tasks,
+ filterStatus,
+ searchQuery,
+ filteredTasks,
+ getTaskCount,
+ }
+}