Luxx/dashboard/src/views/ToolsView.vue

109 lines
4.6 KiB
Vue

<template>
<div class="page-container tools">
<div class="page-header">
<h1>工具管理</h1>
<div class="subtitle">{{ list.length }} 个工具</div>
</div>
<div v-if="loading" class="loading"><div class="spinner"></div>加载中...</div>
<div v-else-if="error" class="error-msg">{{ error }} <button @click="fetchData">重试</button></div>
<div v-else class="table-container">
<table class="tools-table">
<thead>
<tr>
<th>名称</th>
<th>参数</th>
<th class="action-col">启用</th>
</tr>
</thead>
<tbody>
<tr v-for="tool in list" :key="tool.name">
<td class="name-col">
<div class="tool-name">{{ tool.name }}</div>
<div class="tool-desc">{{ tool.description || '-' }}</div>
<div class="tool-category">{{ tool.category }}</div>
</td>
<td class="params-col">
<template v-if="tool.parameters && tool.parameters.properties">
<span v-for="(val, key) in tool.parameters.properties" :key="key" class="param-tag">
{{ key }}
</span>
</template>
<span v-else>-</span>
</td>
<td class="switch-col">
<label class="switch" @click.prevent="toggleEnabled(tool)">
<input type="checkbox" :checked="tool.enabled" />
<span class="slider"></span>
</label>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { toolsAPI } from '../utils/api.js'
const list = ref([])
const loading = ref(true)
const error = ref('')
const fetchData = async () => {
loading.value = true
error.value = ''
try {
const res = await toolsAPI.list()
if (res.success) {
const data = res.data.categorized
const all = []
Object.keys(data || {}).forEach(cat => {
if (Array.isArray(data[cat])) {
all.push(...data[cat].map(t => {
const func = t.function ? t.function : t
return { name: func.name, description: func.description, parameters: func.parameters, category: cat, enabled: t.enabled !== false }
}))
}
})
list.value = all
} else throw new Error(res.message)
} catch (e) { error.value = e.message }
finally { loading.value = false }
}
const toggleEnabled = async (tool) => {
tool.enabled = !tool.enabled
}
onMounted(fetchData)
</script>
<style scoped>
.table-container { background: var(--bg-primary); border: 1px solid var(--border-light); border-radius: 12px; overflow: hidden; }
.tools-table { width: 100%; border-collapse: collapse; }
.tools-table th { text-align: left; padding: 1rem; background: var(--bg-secondary); font-weight: 600; font-size: 0.85rem; color: var(--text-secondary); border-bottom: 1px solid var(--border-light); }
.tools-table td { padding: 1rem; border-bottom: 1px solid var(--border-light); vertical-align: middle; }
.tools-table tr:last-child td { border-bottom: none; }
.tools-table tr:hover td { background: var(--bg-secondary); }
.tool-name { font-weight: 600; font-size: 0.95rem; }
.tool-desc { font-size: 0.8rem; color: var(--text-secondary); margin: 0.25rem 0; }
.tool-category { font-size: 0.7rem; color: var(--text-tertiary); }
.params-col { width: 180px; }
.param-tag { display: inline-block; padding: 0.2rem 0.5rem; background: var(--bg-code); border-radius: 4px; font-size: 0.75rem; color: var(--text-secondary); margin: 0.15rem; }
.switch-col { text-align: center; }
.switch { position: relative; display: inline-block; width: 44px; height: 24px; cursor: pointer; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; cursor: pointer; inset: 0; background-color: #ccc; transition: 0.3s; border-radius: 24px; }
.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: 0.3s; border-radius: 50%; }
input:checked + .slider { background-color: #16a34a; }
input:checked + .slider:before { transform: translateX(20px); }
.loading { text-align: center; padding: 4rem; }
.error-msg { text-align: center; padding: 2rem; color: var(--accent-primary); background: var(--accent-primary-light); border-radius: 12px; }
.spinner { width: 40px; height: 40px; border: 3px solid var(--border-light); border-top-color: var(--accent-primary); border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>