diff --git a/xiaoqu-agent-service/front/src/App.vue b/xiaoqu-agent-service/front/src/App.vue index f17dbe0..ae9c6ec 100644 --- a/xiaoqu-agent-service/front/src/App.vue +++ b/xiaoqu-agent-service/front/src/App.vue @@ -40,6 +40,9 @@ const scopeForm = reactive({ id: null, name: '', code: '', memo: '' }) const scopeToolsDialog = ref(false) const activeScope = ref(null) const selectedToolIds = ref([]) +const scopeToolsTab = ref('') +const scopeToolsKeyword = ref('') +const scopeToolsOnlySelected = ref(false) const title = computed(() => menus.find(item => item.id === current.value)?.label || '') const categories = computed(() => [...new Set(allTools.value.map(tool => tool.category))]) @@ -48,6 +51,27 @@ const groupedTools = computed(() => allTools.value.reduce((groups, tool) => { ;(groups[category] ||= []).push(tool) return groups }, {})) +const scopeToolTabs = computed(() => Object.entries(groupedTools.value).map(([category, items]) => ({ + category, + total: items.length, + selected: items.filter(tool => selectedToolIds.value.includes(tool.id)).length, +}))) +const scopeToolsVisible = computed(() => { + const keyword = scopeToolsKeyword.value.trim().toLowerCase() + const items = scopeToolsTab.value ? (groupedTools.value[scopeToolsTab.value] || []) : allTools.value + return items.filter(tool => { + if (scopeToolsOnlySelected.value && !selectedToolIds.value.includes(tool.id)) return false + if (!keyword) return true + return tool.name.toLowerCase().includes(keyword) || (tool.description || '').toLowerCase().includes(keyword) + }) +}) +const scopeToolsSummary = computed(() => { + const detail = scopeToolTabs.value.filter(tab => tab.selected).map(tab => `${tab.category} ×${tab.selected}`).join(' · ') + return { selected: selectedToolIds.value.length, total: allTools.value.length, detail } +}) +const scopeToolsAllVisibleSelected = computed(() => scopeToolsVisible.value.length > 0 + && scopeToolsVisible.value.every(tool => selectedToolIds.value.includes(tool.id))) +const scopeToolsSomeVisibleSelected = computed(() => scopeToolsVisible.value.some(tool => selectedToolIds.value.includes(tool.id))) const issuedKeyCurl = computed(() => { if (!issuedOpenKey.value) return '' const payload = JSON.stringify({ @@ -276,10 +300,23 @@ async function deleteScope(scope) { async function openScopeTools(scope) { activeScope.value = scope + scopeToolsTab.value = ''; scopeToolsKeyword.value = ''; scopeToolsOnlySelected.value = false ;[selectedToolIds.value] = await Promise.all([api(`/scopes/${scope.id}/tools`), loadAllTools()]) scopeToolsDialog.value = true } +function toggleScopeTool(id, checked) { + const set = new Set(selectedToolIds.value) + if (checked) set.add(id); else set.delete(id) + selectedToolIds.value = [...set] +} + +function toggleScopeToolsVisible(checked) { + const set = new Set(selectedToolIds.value) + scopeToolsVisible.value.forEach(tool => { if (checked) set.add(tool.id); else set.delete(tool.id) }) + selectedToolIds.value = [...set] +} + async function saveScopeTools() { await run(async () => { await api(`/scopes/${activeScope.value.id}/tools`, { method: 'PUT', body: JSON.stringify({ toolIds: selectedToolIds.value }) }) @@ -548,11 +585,42 @@ onMounted(async () => { - - -

{{ category }}

{{ tool.name }}{{ tool.description }}
-
- + +
+ + 仅看已选 +
+ + + + + + + + + + + + + + + + + + +
diff --git a/xiaoqu-agent-service/front/src/style.css b/xiaoqu-agent-service/front/src/style.css index 0244308..609c252 100644 --- a/xiaoqu-agent-service/front/src/style.css +++ b/xiaoqu-agent-service/front/src/style.css @@ -52,13 +52,14 @@ code { font-family: Menlo, Consolas, monospace; font-size: 12px; } .audit-toolbar .el-input { width: 150px; } .audit-toolbar .el-select { width: 140px; } .audit-toolbar .el-date-editor { width: 150px; } -.tool-groups { display: block; max-height: 520px; overflow: auto; } -.tool-group { margin-bottom: 12px; border: 1px solid #ebeef5; border-radius: 4px; } -.tool-group h3 { margin: 0; padding: 9px 12px; font-size: 14px; background: #fafafa; border-bottom: 1px solid #ebeef5; } -.tool-group .el-checkbox { width: 100%; height: auto; margin: 0; padding: 9px 12px; align-items: flex-start; } -.tool-group .el-checkbox + .el-checkbox { border-top: 1px solid #f2f3f5; } -.tool-group .el-checkbox__label { display: flex; flex-direction: column; white-space: normal; } -.tool-group small { margin-top: 3px; color: #909399; } +.scope-tools-toolbar { display: flex; gap: 12px; align-items: center; } +.scope-tools-toolbar .el-input { max-width: 260px; } +.scope-tools-tabs { margin-top: 4px; } +.scope-tools-tabs .el-tabs__header { margin-bottom: 8px; } +.tab-badge { display: inline-block; margin-left: 6px; padding: 0 6px; font-size: 12px; line-height: 16px; border-radius: 8px; background: #f0f9eb; color: #67c23a; vertical-align: 2px; } +.scope-tools-footer { display: flex; align-items: center; justify-content: space-between; width: 100%; } +.scope-tools-summary { color: #606266; font-size: 13px; text-align: left; } +.el-table .row-selected { background: #ecf5ff; } .token-secret { display: block; margin-top: 14px; padding: 14px; word-break: break-all; border-radius: 4px; background: #f4f4f5; font-size: 13px; } .issued-row { display: flex; align-items: center; gap: 10px; margin-top: 12px; } .issued-row .token-secret { flex: 1; margin-top: 0; padding: 9px 12px; }