11<script setup lang="ts">
22import { ref , computed , onMounted } from ' vue'
33import { useI18n } from ' vue-i18n'
4- import { useResponsive } from ' ../composables/useResponsive'
54import { apiFetch } from ' @/api'
65import { store , applyMcpList } from ' @/store'
7- import { useToast , SButton , SInput , SSelect , SModal , SFormItem , SFormSection , STabBar , STab , SPageToolbar , SPageContent } from ' sbot-ui'
6+ import { useToast , SButton , SInput , SSelect , SModal , SFormItem , SFormSection , STabBar , STab , SPageToolbar , SPageContent , STable , type STableColumn } from ' sbot-ui'
87import { McpTransport } from ' @/types'
98import type { McpEntry , McpTool , McpPrompt , McpResource , McpResourceTemplate } from ' @/types'
109import { serverAddr } from ' @/utils/mcpSchema'
@@ -13,11 +12,17 @@ import { sourceBadgeStyle } from '@/utils/badges'
1312
1413const { t } = useI18n ()
1514const { show } = useToast ()
16- const { isMobile } = useResponsive ()
1715
1816const searchQuery = ref (' ' )
1917const activeTab = ref (' all' )
2018
19+ const columns = computed <STableColumn []>(() => [
20+ { key: ' name' , label: t (' common.name' ), primary: true , ellipsis: true , width: ' 220px' },
21+ { key: ' description' , label: t (' common.description' ), ellipsis: true },
22+ { key: ' address' , label: t (' mcp.address_col' ), ellipsis: true , width: ' 260px' },
23+ { key: ' ops' , label: t (' common.ops' ), ops: true , width: ' 190px' },
24+ ])
25+
2126const sources = computed (() => {
2227 const seen = new Set <string >()
2328 for (const m of store .allMcps ) if (m .source ) seen .add (m .source )
@@ -226,65 +231,30 @@ onMounted(load)
226231 </STabBar >
227232
228233 <SPageContent >
229- <table v-if =" !isMobile" class =" mcp-table" >
230- <colgroup >
231- <col style =" width :220px " />
232- <col />
233- <col style =" width :260px " />
234- <col style =" width :190px " />
235- </colgroup >
236- <thead >
237- <tr >
238- <th >{{ t('common.name') }}</th >
239- <th >{{ t('common.description') }}</th >
240- <th >{{ t('mcp.address_col') }}</th >
241- <th >{{ t('common.ops') }}</th >
242- </tr >
243- </thead >
244- <tbody >
245- <tr v-if =" filteredMcps.length === 0" >
246- <td colspan =" 4" class =" mcp-empty" >
247- {{ searchQuery.trim() ? t('mcp.no_match') : t('mcp.empty') }}
248- </td >
249- </tr >
250- <tr v-for =" m in filteredMcps" :key =" m.id" >
251- <td class =" mcp-name" >
252- <span :style =" `font-size:10px;padding:1px 6px;border-radius:8px;font-weight:600;margin-right:6px;${sourceBadgeStyle(m.source)}`" >{{ m.source }}</span >{{ m.name }}
253- </td >
254- <td class =" mcp-desc" >{{ m.description || '—' }}</td >
255- <td class =" mcp-addr" >{{ serverAddr(m as any) || '—' }}</td >
256- <td class =" mcp-ops" >
257- <div class =" ops-cell" >
258- <SButton type="outline" size="sm" @click =" viewTools (m .id )" >{{ t('common.view') }}</SButton >
259- <SButton v-if =" m .source !== ' 内置' " type="outline" size="sm" @click =" openEdit (m .id )" >{{ t('common.edit') }}</SButton >
260- <SButton v-if =" m .source !== ' 内置' " type="danger" size="sm" @click =" remove (m .id )" >{{ t('common.delete') }}</SButton >
261- </div >
262- </td >
263- </tr >
264- </tbody >
265- </table >
266- <div v-else class =" card-list" >
267- <div v-for =" m in filteredMcps" :key =" m.id" class =" mobile-card" >
268- <div class =" mobile-card-header" >
269- <span :style =" `font-size:10px;padding:1px 6px;border-radius:8px;font-weight:600;margin-right:6px;${sourceBadgeStyle(m.source)}`" >{{ m.source }}</span >
270- {{ m.name }}
271- </div >
272- <div class =" mobile-card-fields" >
273- <span class =" mobile-card-label" >{{ t('common.description') }}</span >
274- <span class =" mobile-card-value" >{{ m.description || '—' }}</span >
275- <span class =" mobile-card-label" >{{ t('mcp.address_col') }}</span >
276- <span class =" mobile-card-value mcp-addr-mobile" >{{ serverAddr(m as any) || '—' }}</span >
234+ <STable
235+ :columns =" columns "
236+ :rows =" filteredMcps "
237+ row-key="id"
238+ :empty-text =" searchQuery .trim () ? t (' mcp.no_match' ) : t (' mcp.empty' )"
239+ >
240+ <template #name =" { row } " >
241+ <span :style =" `font-size:10px;padding:1px 6px;border-radius:8px;font-weight:600;margin-right:6px;${sourceBadgeStyle(row.source)}`" >{{ row.source }}</span >
242+ <span style =" font-family :var (--sui-font-mono )" >{{ row.name }}</span >
243+ </template >
244+ <template #description =" { row } " >
245+ <span class =" mcp-desc" >{{ row.description || '—' }}</span >
246+ </template >
247+ <template #address =" { row } " >
248+ <span class =" mcp-addr" >{{ serverAddr(row as any) || '—' }}</span >
249+ </template >
250+ <template #ops =" { row } " >
251+ <div class =" ops-cell" >
252+ <SButton type="outline" size="sm" @click =" viewTools (row .id )" >{{ t('common.view') }}</SButton >
253+ <SButton v-if =" row .source !== ' 内置' " type="outline" size="sm" @click =" openEdit (row .id )" >{{ t('common.edit') }}</SButton >
254+ <SButton v-if =" row .source !== ' 内置' " type="danger" size="sm" @click =" remove (row .id )" >{{ t('common.delete') }}</SButton >
277255 </div >
278- <div class =" mobile-card-ops" >
279- <SButton type="outline" size="sm" @click =" viewTools (m .id )" >{{ t('common.view') }}</SButton >
280- <SButton v-if =" m .source !== ' 内置' " type="outline" size="sm" @click =" openEdit (m .id )" >{{ t('common.edit') }}</SButton >
281- <SButton v-if =" m .source !== ' 内置' " type="danger" size="sm" @click =" remove (m .id )" >{{ t('common.delete') }}</SButton >
282- </div >
283- </div >
284- <div v-if =" filteredMcps.length === 0" class =" mobile-card-empty" >
285- {{ searchQuery.trim() ? t('mcp.no_match') : t('mcp.empty') }}
286- </div >
287- </div >
256+ </template >
257+ </STable >
288258 </SPageContent >
289259
290260 <!-- Edit / Add MCP Modal -->
@@ -381,34 +351,14 @@ onMounted(load)
381351<style scoped>
382352.tab-bar-spacer { flex : 1 ; }
383353.mcp-search { width : 220px ; }
384- .mcp-table { width : 100% ; table-layout : fixed ; }
385- .mcp-empty {
386- text-align : center ;
387- color : var (--sui-fg-disabled );
388- padding : 40px ;
389- }
390- .mcp-name {
391- font-family : var (--sui-font-mono );
392- overflow : hidden ;
393- text-overflow : ellipsis ;
394- white-space : nowrap ;
395- }
396354.mcp-desc {
397355 color : var (--sui-fg-muted );
398356 font-size : var (--sui-fs-sm );
399- overflow : hidden ;
400- text-overflow : ellipsis ;
401- white-space : nowrap ;
402357}
403358.mcp-addr {
404- overflow : hidden ;
405- text-overflow : ellipsis ;
406- white-space : nowrap ;
407359 color : var (--sui-fg-disabled );
408360 font-size : var (--sui-fs-sm );
409361}
410- .mcp-addr-mobile { word-break : break-all ; }
411- .mcp-ops { white-space : nowrap ; }
412362
413363.kv-row {
414364 display : flex ;
0 commit comments