- 添加 Tools.vue 工具管理页面 - 重构 Skill.vue 代码 - 更新路由和侧边栏导航 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
240 lines
10 KiB
Vue
240 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import { useSkills } from './skill/useSkills'
|
|
import '@/views/database/database.css'
|
|
|
|
const {
|
|
searchQuery,
|
|
filterStatus,
|
|
isEditing,
|
|
isCreating,
|
|
editForm,
|
|
newSkillForm,
|
|
categories,
|
|
types,
|
|
filteredSkills,
|
|
statusClass,
|
|
openCreate,
|
|
closeCreate,
|
|
saveNewSkill,
|
|
openEdit,
|
|
closeEdit,
|
|
saveEdit,
|
|
toggleStatus,
|
|
deleteSkill,
|
|
} = useSkills()
|
|
</script>
|
|
|
|
<template>
|
|
<div class="p-6 min-h-screen">
|
|
<!-- 顶部导航 -->
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div class="flex items-center gap-2">
|
|
<i class="fa-solid fa-wand-magic-sparkles text-orange-500"></i>
|
|
<span class="font-medium">Skills</span>
|
|
</div>
|
|
<button @click="openCreate" class="btn-primary">
|
|
<i class="fa-solid fa-plus"></i>
|
|
New Skill
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 搜索和筛选 -->
|
|
<div class="flex gap-4 mb-6">
|
|
<div class="flex-1 relative">
|
|
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
|
<input
|
|
v-model="searchQuery"
|
|
type="text"
|
|
placeholder="Search skills by name, type or description..."
|
|
class="search-input w-full"
|
|
>
|
|
</div>
|
|
<el-select v-model="filterStatus" placeholder="Select" class="w-40" size="large">
|
|
<el-option label="All Status" value="all" />
|
|
<el-option label="Running" value="running" />
|
|
<el-option label="Stopped" value="stopped" />
|
|
<el-option label="Error" value="error" />
|
|
</el-select>
|
|
</div>
|
|
|
|
<!-- Skills 列表 -->
|
|
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
|
<table v-if="filteredSkills.length > 0" class="w-full">
|
|
<thead class="bg-dark-600">
|
|
<tr>
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Skill Name</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Type</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Category</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Port</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Tools</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="skill in filteredSkills" :key="skill.id" class="table-row">
|
|
<td class="px-5 py-4">
|
|
<div class="font-medium">{{ skill.name }}</div>
|
|
<div class="text-sm text-gray-500">{{ skill.description }}</div>
|
|
</td>
|
|
<td class="px-5 py-4 text-center">
|
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm">{{ skill.type }}</span>
|
|
</td>
|
|
<td class="px-5 py-4 text-center">
|
|
<span class="text-gray-400 text-sm capitalize">{{ skill.category }}</span>
|
|
</td>
|
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.port }}</td>
|
|
<td class="px-5 py-4 text-center">
|
|
<span class="text-primary-cyan">{{ skill.tools }}</span>
|
|
</td>
|
|
<td class="px-5 py-4 text-center">
|
|
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" :class="skill.status === 'running' ? 'bg-green-500/20 text-green-400' : skill.status === 'error' ? 'bg-red-500/20 text-red-400' : 'bg-gray-500/20 text-gray-400'">
|
|
<span class="w-1.5 h-1.5 rounded-full" :class="statusClass(skill.status)"></span>
|
|
<span class="capitalize">{{ skill.status }}</span>
|
|
</span>
|
|
</td>
|
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ skill.createdAt }}</td>
|
|
<td class="px-5 py-4">
|
|
<div class="flex items-center justify-center gap-2">
|
|
<button @click="toggleStatus(skill)" class="btn-icon" :title="skill.status === 'running' ? 'Stop' : 'Start'">
|
|
<i :class="['fa-solid', skill.status === 'running' ? 'fa-stop' : 'fa-play', 'text-gray-400 hover:text-white']"></i>
|
|
</button>
|
|
<button @click="openEdit(skill)" class="btn-icon" title="Edit">
|
|
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
|
</button>
|
|
<button @click="deleteSkill(skill.id)" class="btn-icon" title="Delete">
|
|
<i class="fa-solid fa-trash text-gray-400 hover:text-red-400"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- 空状态 -->
|
|
<div v-else class="empty-box">
|
|
<div class="empty-icon">
|
|
<i class="fa-solid fa-wand-magic-sparkles"></i>
|
|
</div>
|
|
<p class="empty-text">No skills found</p>
|
|
<p class="empty-tip">Click "New Skill" to add a skill</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 编辑弹窗 -->
|
|
<Teleport to="body">
|
|
<div v-if="isEditing" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50 modal-overlay">
|
|
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl modal-content">
|
|
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
|
<h3 class="text-lg font-semibold">Edit Skill</h3>
|
|
<button @click="closeEdit" class="btn-icon">
|
|
<i class="fa-solid fa-xmark text-xl"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="p-5 space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Skill Name</label>
|
|
<input v-model="editForm.name" type="text" class="input-field">
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
|
<el-select v-model="editForm.type" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
|
<el-option v-for="type in types" :key="type" :label="type" :value="type" />
|
|
</el-select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Category</label>
|
|
<el-select v-model="editForm.category" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
|
<el-option v-for="cat in categories" :key="cat.value" :label="cat.label" :value="cat.value" />
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Port</label>
|
|
<input v-model="editForm.port" type="number" class="input-field">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
|
<textarea v-model="editForm.description" rows="2" class="input-field resize-none"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
|
<button @click="closeEdit" class="btn-secondary">Cancel</button>
|
|
<button @click="saveEdit" class="btn-primary">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
|
|
<!-- 新建弹窗 -->
|
|
<Teleport to="body">
|
|
<div v-if="isCreating" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4 modal-overlay">
|
|
<div class="bg-dark-800 rounded-2xl w-full max-w-lg border border-dark-600 shadow-2xl overflow-hidden modal-content">
|
|
<div class="flex items-center justify-between p-5 border-b border-dark-600 bg-dark-700/50">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center">
|
|
<i class="fa-solid fa-wand-magic-sparkles text-white"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-xl font-semibold text-white">Create New Skill</h3>
|
|
<p class="text-sm text-gray-400">Configure your skill</p>
|
|
</div>
|
|
</div>
|
|
<button @click="closeCreate" class="btn-icon">
|
|
<i class="fa-solid fa-xmark text-xl"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="p-5 space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Skill Name</label>
|
|
<input v-model="newSkillForm.name" type="text" placeholder="Enter skill name..." class="input-field">
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
|
<el-select v-model="newSkillForm.type" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
|
<el-option v-for="type in types" :key="type" :label="type" :value="type" />
|
|
</el-select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Category</label>
|
|
<el-select v-model="newSkillForm.category" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
|
<el-option v-for="cat in categories" :key="cat.value" :label="cat.label" :value="cat.value" />
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Port</label>
|
|
<input v-model="newSkillForm.port" type="number" placeholder="3000" class="input-field">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
|
<textarea v-model="newSkillForm.description" rows="2" placeholder="Describe this skill..." class="input-field resize-none"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-600 bg-dark-700/50">
|
|
<button @click="closeCreate" class="btn-secondary">Cancel</button>
|
|
<button @click="saveNewSkill" class="btn-primary">Create Skill</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* 使用全局样式 */
|
|
</style>
|