服务密钥调用方 @@ -456,9 +487,11 @@ onMounted(async () => { 服务密钥用于调用 /api/open/token/ensure 换取业务 Token;作用域决定该密钥能为哪些数据域签发或获取 Token。 + - + + @@ -484,9 +517,11 @@ onMounted(async () => { data-testid="admin-tool-pager" @current-change="changeToolPage" /> “停用”是全局紧急开关,生效后任何 Token 都无法调用该工具。 + - + + 作用域+ 新建作用域 @@ -505,9 +540,11 @@ onMounted(async () => { + - + + @@ -535,6 +572,7 @@ onMounted(async () => { + diff --git a/xiaoqu-agent-service/front/src/style.css b/xiaoqu-agent-service/front/src/style.css index 609c252..22d6dd9 100644 --- a/xiaoqu-agent-service/front/src/style.css +++ b/xiaoqu-agent-service/front/src/style.css @@ -24,6 +24,12 @@ button, input { font: inherit; } .admin-user { display: flex; align-items: center; gap: 8px; color: #606266; } .avatar { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%; color: #409eff; background: #ecf5ff; } .content { padding: 20px; } +.tabbar { display: flex; align-items: flex-end; gap: 6px; padding: 8px 16px 0; overflow-x: auto; background: #f0f2f5; } +.tabbar-tab { display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px 7px 14px; border: 1px solid #dcdfe6; border-bottom-color: transparent; border-radius: 6px 6px 0 0; color: #606266; background: #fafafa; cursor: pointer; font-size: 13px; white-space: nowrap; user-select: none; } +.tabbar-tab:hover { color: #409eff; } +.tabbar-tab.active { color: #409eff; background: white; border-color: #dcdfe6; border-bottom-color: white; box-shadow: 0 -1px 2px rgb(0 0 0 / 4%); } +.tabbar-close { display: inline-grid; place-items: center; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; color: #909399; background: transparent; cursor: pointer; font-size: 13px; line-height: 1; } +.tabbar-close:hover { color: white; background: #c0c4cc; } .card { margin-bottom: 16px; padding: 20px; border-radius: 5px; background: white; box-shadow: 0 1px 2px rgb(0 0 0 / 4%); } .card-title { min-height: 32px; margin-bottom: 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 15px; font-weight: 650; } .card-title small { margin-left: 5px; color: #909399; font-size: 12px; font-weight: 400; } diff --git a/xiaoqu-agent-service/front/tests/source-contract.test.mjs b/xiaoqu-agent-service/front/tests/source-contract.test.mjs index 2a6c4c6..4e2cefa 100644 --- a/xiaoqu-agent-service/front/tests/source-contract.test.mjs +++ b/xiaoqu-agent-service/front/tests/source-contract.test.mjs @@ -29,3 +29,16 @@ test('prototype interactions expose stable test ids', async () => { assert.match(source, new RegExp(`data-testid=["']${testId}["']`), testId) } }) + +test('multi-tab navigation exposes tab bar and close controls', async () => { + const source = await readFile(new URL('../src/App.vue', import.meta.url), 'utf8') + assert.match(source, /data-testid=["']admin-tabbar["']/, 'admin-tabbar') + assert.match(source, /admin-tab-\$\{tab\.id\}/, 'admin-tab-*') + assert.match(source, /admin-tab-close-\$\{tab\.id\}/, 'admin-tab-close-*') + assert.match(source, /openTabs\.includes\('tokens'\)/, 'tokens panel gated by openTabs') + assert.match(source, /openTabs\.includes\('clients'\)/, 'clients panel gated by openTabs') + assert.match(source, /openTabs\.includes\('tools'\)/, 'tools panel gated by openTabs') + assert.match(source, /openTabs\.includes\('scopes'\)/, 'scopes panel gated by openTabs') + assert.match(source, /openTabs\.includes\('audit'\)/, 'audit panel gated by openTabs') + assert.match(source, /function closeTab\(/, 'closeTab handler') +})
@@ -484,9 +517,11 @@ onMounted(async () => { data-testid="admin-tool-pager" @current-change="changeToolPage" /> “停用”是全局紧急开关,生效后任何 Token 都无法调用该工具。 + - + + 作用域+ 新建作用域 @@ -505,9 +540,11 @@ onMounted(async () => { + - + + @@ -535,6 +572,7 @@ onMounted(async () => { + diff --git a/xiaoqu-agent-service/front/src/style.css b/xiaoqu-agent-service/front/src/style.css index 609c252..22d6dd9 100644 --- a/xiaoqu-agent-service/front/src/style.css +++ b/xiaoqu-agent-service/front/src/style.css @@ -24,6 +24,12 @@ button, input { font: inherit; } .admin-user { display: flex; align-items: center; gap: 8px; color: #606266; } .avatar { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%; color: #409eff; background: #ecf5ff; } .content { padding: 20px; } +.tabbar { display: flex; align-items: flex-end; gap: 6px; padding: 8px 16px 0; overflow-x: auto; background: #f0f2f5; } +.tabbar-tab { display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px 7px 14px; border: 1px solid #dcdfe6; border-bottom-color: transparent; border-radius: 6px 6px 0 0; color: #606266; background: #fafafa; cursor: pointer; font-size: 13px; white-space: nowrap; user-select: none; } +.tabbar-tab:hover { color: #409eff; } +.tabbar-tab.active { color: #409eff; background: white; border-color: #dcdfe6; border-bottom-color: white; box-shadow: 0 -1px 2px rgb(0 0 0 / 4%); } +.tabbar-close { display: inline-grid; place-items: center; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; color: #909399; background: transparent; cursor: pointer; font-size: 13px; line-height: 1; } +.tabbar-close:hover { color: white; background: #c0c4cc; } .card { margin-bottom: 16px; padding: 20px; border-radius: 5px; background: white; box-shadow: 0 1px 2px rgb(0 0 0 / 4%); } .card-title { min-height: 32px; margin-bottom: 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 15px; font-weight: 650; } .card-title small { margin-left: 5px; color: #909399; font-size: 12px; font-weight: 400; } diff --git a/xiaoqu-agent-service/front/tests/source-contract.test.mjs b/xiaoqu-agent-service/front/tests/source-contract.test.mjs index 2a6c4c6..4e2cefa 100644 --- a/xiaoqu-agent-service/front/tests/source-contract.test.mjs +++ b/xiaoqu-agent-service/front/tests/source-contract.test.mjs @@ -29,3 +29,16 @@ test('prototype interactions expose stable test ids', async () => { assert.match(source, new RegExp(`data-testid=["']${testId}["']`), testId) } }) + +test('multi-tab navigation exposes tab bar and close controls', async () => { + const source = await readFile(new URL('../src/App.vue', import.meta.url), 'utf8') + assert.match(source, /data-testid=["']admin-tabbar["']/, 'admin-tabbar') + assert.match(source, /admin-tab-\$\{tab\.id\}/, 'admin-tab-*') + assert.match(source, /admin-tab-close-\$\{tab\.id\}/, 'admin-tab-close-*') + assert.match(source, /openTabs\.includes\('tokens'\)/, 'tokens panel gated by openTabs') + assert.match(source, /openTabs\.includes\('clients'\)/, 'clients panel gated by openTabs') + assert.match(source, /openTabs\.includes\('tools'\)/, 'tools panel gated by openTabs') + assert.match(source, /openTabs\.includes\('scopes'\)/, 'scopes panel gated by openTabs') + assert.match(source, /openTabs\.includes\('audit'\)/, 'audit panel gated by openTabs') + assert.match(source, /function closeTab\(/, 'closeTab handler') +})
@@ -535,6 +572,7 @@ onMounted(async () => { + diff --git a/xiaoqu-agent-service/front/src/style.css b/xiaoqu-agent-service/front/src/style.css index 609c252..22d6dd9 100644 --- a/xiaoqu-agent-service/front/src/style.css +++ b/xiaoqu-agent-service/front/src/style.css @@ -24,6 +24,12 @@ button, input { font: inherit; } .admin-user { display: flex; align-items: center; gap: 8px; color: #606266; } .avatar { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%; color: #409eff; background: #ecf5ff; } .content { padding: 20px; } +.tabbar { display: flex; align-items: flex-end; gap: 6px; padding: 8px 16px 0; overflow-x: auto; background: #f0f2f5; } +.tabbar-tab { display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px 7px 14px; border: 1px solid #dcdfe6; border-bottom-color: transparent; border-radius: 6px 6px 0 0; color: #606266; background: #fafafa; cursor: pointer; font-size: 13px; white-space: nowrap; user-select: none; } +.tabbar-tab:hover { color: #409eff; } +.tabbar-tab.active { color: #409eff; background: white; border-color: #dcdfe6; border-bottom-color: white; box-shadow: 0 -1px 2px rgb(0 0 0 / 4%); } +.tabbar-close { display: inline-grid; place-items: center; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; color: #909399; background: transparent; cursor: pointer; font-size: 13px; line-height: 1; } +.tabbar-close:hover { color: white; background: #c0c4cc; } .card { margin-bottom: 16px; padding: 20px; border-radius: 5px; background: white; box-shadow: 0 1px 2px rgb(0 0 0 / 4%); } .card-title { min-height: 32px; margin-bottom: 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 15px; font-weight: 650; } .card-title small { margin-left: 5px; color: #909399; font-size: 12px; font-weight: 400; } diff --git a/xiaoqu-agent-service/front/tests/source-contract.test.mjs b/xiaoqu-agent-service/front/tests/source-contract.test.mjs index 2a6c4c6..4e2cefa 100644 --- a/xiaoqu-agent-service/front/tests/source-contract.test.mjs +++ b/xiaoqu-agent-service/front/tests/source-contract.test.mjs @@ -29,3 +29,16 @@ test('prototype interactions expose stable test ids', async () => { assert.match(source, new RegExp(`data-testid=["']${testId}["']`), testId) } }) + +test('multi-tab navigation exposes tab bar and close controls', async () => { + const source = await readFile(new URL('../src/App.vue', import.meta.url), 'utf8') + assert.match(source, /data-testid=["']admin-tabbar["']/, 'admin-tabbar') + assert.match(source, /admin-tab-\$\{tab\.id\}/, 'admin-tab-*') + assert.match(source, /admin-tab-close-\$\{tab\.id\}/, 'admin-tab-close-*') + assert.match(source, /openTabs\.includes\('tokens'\)/, 'tokens panel gated by openTabs') + assert.match(source, /openTabs\.includes\('clients'\)/, 'clients panel gated by openTabs') + assert.match(source, /openTabs\.includes\('tools'\)/, 'tools panel gated by openTabs') + assert.match(source, /openTabs\.includes\('scopes'\)/, 'scopes panel gated by openTabs') + assert.match(source, /openTabs\.includes\('audit'\)/, 'audit panel gated by openTabs') + assert.match(source, /function closeTab\(/, 'closeTab handler') +})