refactor: 重构 Plan 页面代码结构
- 抽取 usePlan composable 逻辑 - 分离 plan.css 样式文件 - 简化 Plan.vue 组件代码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,70 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import { Play, Pause, Edit, Trash2, Plus, Search, Clock } from 'lucide-vue-next'
|
import { Play, Pause, Edit, Trash2, Plus, Search, Clock } from 'lucide-vue-next'
|
||||||
|
import { usePlan } from './plan/usePlan'
|
||||||
|
import './plan/plan.css'
|
||||||
|
|
||||||
// Mock scheduled tasks data
|
const {
|
||||||
const tasks = ref([
|
filterStatus,
|
||||||
{
|
searchQuery,
|
||||||
id: 1,
|
filteredTasks,
|
||||||
name: 'Human-like Heartbeat',
|
} = usePlan()
|
||||||
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') // running, stopped, 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
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -170,99 +113,3 @@ const getTaskCount = (status: string) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.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 {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空状态样式 */
|
|
||||||
.empty-box {
|
|
||||||
min-height: 340px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: linear-gradient(135deg, #1f2937, #111827);
|
|
||||||
border-radius: 24px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-icon i {
|
|
||||||
font-size: 40px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
color: #d1d5db;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-tip {
|
|
||||||
color: #6b7280;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
92
web/src/views/plan/plan.css
Normal file
92
web/src/views/plan/plan.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
87
web/src/views/plan/usePlan.ts
Normal file
87
web/src/views/plan/usePlan.ts
Normal file
@@ -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<PlanTask[]>([
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user