补齐平台模板与场景 DSL、预算控制、执行看板和企业场景脚本,增强 Windows 启动/迁移与前端代理和聊天会话记忆,修复执行创建阶段 500 与异步链路排障体验。 Made-with: Cursor
170 lines
4.8 KiB
Vue
170 lines
4.8 KiB
Vue
<template>
|
||
<MainLayout>
|
||
<div class="main-console">
|
||
<el-row :gutter="20" class="hero-row">
|
||
<el-col :span="12">
|
||
<el-card shadow="hover" class="panel-card" @click="router.push('/template-market')">
|
||
<div class="panel-inner">
|
||
<el-icon class="panel-icon" :size="40"><Star /></el-icon>
|
||
<div>
|
||
<h2>应用商店</h2>
|
||
<p class="muted">浏览模板市场,从模板快速创建工作流或 Agent。</p>
|
||
<el-button type="primary" text>进入模板市场 →</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-card shadow="hover" class="panel-card" @click="router.push('/executions')">
|
||
<div class="panel-inner">
|
||
<el-icon class="panel-icon" :size="40"><List /></el-icon>
|
||
<div>
|
||
<h2>运行看板</h2>
|
||
<p class="muted">查看执行历史、父子链路与审批挂起(awaiting_approval)状态。</p>
|
||
<el-button type="primary" text>打开执行历史 →</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="20" class="hero-row">
|
||
<el-col :span="24">
|
||
<el-card shadow="hover" class="panel-card slim" @click="router.push('/execution-board')">
|
||
<div class="panel-inner">
|
||
<el-icon class="panel-icon" :size="36"><Histogram /></el-icon>
|
||
<div>
|
||
<h2>父子执行链看板</h2>
|
||
<p class="muted">输入根执行 ID,查看子工作流 / invoke_agent 整棵执行树与状态汇总。</p>
|
||
<el-button type="primary" text>打开看板 →</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-card class="recent-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>最近执行</span>
|
||
<el-button size="small" :loading="loading" @click="loadRecent">刷新</el-button>
|
||
</div>
|
||
</template>
|
||
<el-table :data="recent" v-loading="loading" style="width: 100%" @row-click="onRow">
|
||
<el-table-column prop="id" label="执行 ID" width="220">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="goDetail(row.id)">
|
||
{{ row.id.substring(0, 8) }}…
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="130">
|
||
<template #default="{ row }">
|
||
<el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="depth" label="深度" width="72" />
|
||
<el-table-column prop="created_at" label="创建时间" />
|
||
</el-table>
|
||
</el-card>
|
||
</div>
|
||
</MainLayout>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import MainLayout from '@/components/MainLayout.vue'
|
||
import { Star, List, Histogram } from '@element-plus/icons-vue'
|
||
import { useExecutionStore } from '@/stores/execution'
|
||
|
||
const router = useRouter()
|
||
const executionStore = useExecutionStore()
|
||
const recent = ref<any[]>([])
|
||
const loading = ref(false)
|
||
|
||
const loadRecent = async () => {
|
||
loading.value = true
|
||
try {
|
||
await executionStore.fetchExecutions({ limit: 15, skip: 0 })
|
||
recent.value = executionStore.executions.slice(0, 15)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const statusType = (s: string) => {
|
||
const m: Record<string, string> = {
|
||
pending: 'info',
|
||
running: 'warning',
|
||
completed: 'success',
|
||
failed: 'danger',
|
||
awaiting_approval: 'warning'
|
||
}
|
||
return m[s] || 'info'
|
||
}
|
||
|
||
const statusText = (s: string) => {
|
||
const m: Record<string, string> = {
|
||
pending: '等待中',
|
||
running: '执行中',
|
||
completed: '已完成',
|
||
failed: '失败',
|
||
awaiting_approval: '待审批'
|
||
}
|
||
return m[s] || s
|
||
}
|
||
|
||
const goDetail = (id: string) => {
|
||
router.push(`/executions/${id}`)
|
||
}
|
||
|
||
const onRow = (row: any) => {
|
||
goDetail(row.id)
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadRecent()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.main-console {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
.hero-row {
|
||
margin-bottom: 20px;
|
||
}
|
||
.panel-card {
|
||
cursor: pointer;
|
||
min-height: 140px;
|
||
}
|
||
.panel-card.slim {
|
||
min-height: auto;
|
||
}
|
||
.panel-inner {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: flex-start;
|
||
}
|
||
.panel-icon {
|
||
color: var(--el-color-primary);
|
||
flex-shrink: 0;
|
||
}
|
||
.muted {
|
||
color: var(--el-text-color-secondary);
|
||
margin: 8px 0;
|
||
font-size: 14px;
|
||
}
|
||
.recent-card .card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
h2 {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
}
|
||
</style>
|