Files
map-client-vue/web/src/SimpleApp.vue
2025-10-14 21:52:11 +08:00

757 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<n-config-provider :theme="theme" :theme-overrides="themeOverrides">
<n-global-style />
<n-message-provider>
<div class="app">
<!-- 侧边栏 -->
<div class="sidebar" :class="{ collapsed: sidebarCollapsed }">
<div class="sidebar-header">
<div class="app-logo">
<n-icon size="24" color="#3b82f6">
<Robot />
</n-icon>
<span v-if="!sidebarCollapsed" class="app-title">MCP Client</span>
</div>
<div class="header-actions">
<n-button
v-if="!sidebarCollapsed"
quaternary
circle
@click="toggleTheme"
class="theme-toggle"
>
<template #icon>
<n-icon>
<Sun v-if="isDark" />
<Moon v-else />
</n-icon>
</template>
</n-button>
<n-button
quaternary
circle
@click="toggleSidebar"
class="collapse-toggle"
>
<template #icon>
<n-icon>
<Menu2 />
</n-icon>
</template>
</n-button>
</div>
</div>
<n-scrollbar class="sidebar-content">
<div class="nav-section">
<div v-if="!sidebarCollapsed" class="section-title">核心功能</div>
<div class="nav-items">
<div
class="nav-item"
:class="{ active: currentRoute === 'chat' }"
@click="currentRoute = 'chat'"
>
<n-icon size="18">
<MessageCircle />
</n-icon>
<span>聊天对话</span>
<div class="nav-indicator" v-if="currentRoute === 'chat'"></div>
</div>
<div
class="nav-item"
:class="{ active: currentRoute === 'tools' }"
@click="currentRoute = 'tools'"
>
<n-icon size="18">
<Tool />
</n-icon>
<span>工具管理</span>
<div class="nav-indicator" v-if="currentRoute === 'tools'"></div>
</div>
<div
class="nav-item"
:class="{ active: currentRoute === 'data' }"
@click="currentRoute = 'data'"
>
<n-icon size="18">
<Database />
</n-icon>
<span>数据管理</span>
<div class="nav-indicator" v-if="currentRoute === 'data'"></div>
</div>
</div>
</div>
<n-divider style="margin: 16px 0;" />
<div class="nav-section">
<div class="section-title">设置</div>
<div class="nav-items">
<div
class="nav-item"
:class="{ active: currentRoute === 'model-providers' }"
@click="currentRoute = 'model-providers'"
>
<n-icon size="18">
<Brain />
</n-icon>
<span>模型服务</span>
<div class="nav-indicator" v-if="currentRoute === 'model-providers'"></div>
</div>
<div
class="nav-item"
:class="{ active: currentRoute === 'display-settings' }"
@click="currentRoute = 'display-settings'"
>
<n-icon size="18">
<Palette />
</n-icon>
<span>显示设置</span>
<div class="nav-indicator" v-if="currentRoute === 'display-settings'"></div>
</div>
<div
class="nav-item"
:class="{ active: currentRoute === 'mcp' }"
@click="currentRoute = 'mcp'"
>
<n-icon size="18">
<Settings />
</n-icon>
<span>MCP 设置</span>
<div class="nav-indicator" v-if="currentRoute === 'mcp'"></div>
</div>
</div>
</div>
</n-scrollbar>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 聊天页面 -->
<ChatLayout v-if="currentRoute === 'chat'" />
<!-- 工具页面 -->
<ToolsManager v-else-if="currentRoute === 'tools'" />
<!-- 数据页面 -->
<DataManager v-else-if="currentRoute === 'data'" />
<!-- 模型服务页面 -->
<ModelService v-else-if="currentRoute === 'model-providers'" />
<!-- 显示设置页面 -->
<!-- 显示设置页面 -->
<DisplaySettings v-else-if="currentRoute === 'display-settings'" />
<!-- MCP 设置页面 -->
<MCPSettings v-else-if="currentRoute === 'mcp'" />
<!-- 默认首页 -->
<div v-else class="content-page">
<div class="welcome-header">
<div class="welcome-logo">
<n-icon size="48" color="#3b82f6">
<Robot />
</n-icon>
</div>
<h1>欢迎使用 MCP Vue Client</h1>
<p>现代化的模型上下文协议客户端</p>
</div>
<div class="welcome-grid">
<n-card
class="welcome-card"
hoverable
@click="currentRoute = 'chat'"
>
<template #cover>
<div class="card-icon chat-icon">
<n-icon size="32">
<MessageCircle />
</n-icon>
</div>
</template>
<h3>开始对话</h3>
<p>与AI模型进行智能对话体验强大的语言理解能力</p>
</n-card>
<n-card
class="welcome-card"
hoverable
@click="currentRoute = 'tools'"
>
<template #cover>
<div class="card-icon tools-icon">
<n-icon size="32">
<Tool />
</n-icon>
</div>
</template>
<h3>使用工具</h3>
<p>执行各种MCP工具扩展AI的能力边界</p>
</n-card>
<n-card
class="welcome-card"
hoverable
@click="currentRoute = 'mcp'"
>
<template #cover>
<div class="card-icon mcp-icon">
<n-icon size="32">
<Settings />
</n-icon>
</div>
</template>
<h3>配置服务</h3>
<p>管理MCP服务器连接搭建强大的AI生态</p>
</n-card>
</div>
<div class="stats-overview">
<n-card title="系统概览">
<div class="stats-grid">
<n-statistic label="已连接服务器" :value="0" />
<n-statistic label="可用工具" :value="0" />
<n-statistic label="对话次数" :value="0" />
<n-statistic label="配置模型" :value="0" />
</div>
</n-card>
</div>
</div>
</div>
</div>
</n-message-provider>
</n-config-provider>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { darkTheme, type GlobalTheme } from 'naive-ui'
import {
MessageCircle,
Tool,
Database,
Cpu as Brain,
Palette,
Settings,
Sun,
Moon,
Robot,
Menu2
} from '@vicons/tabler'
import DisplaySettings from './components/DisplaySettings.vue'
import MCPSettings from './components/MCPSettings.vue'
import ModelService from './components/ModelService.vue'
import ToolsManager from './components/ToolsManager.vue'
import DataManager from './components/DataManager.vue'
import ChatLayout from './components/Chat/ChatLayout.vue'
import { useModelStore } from './stores/modelStore'
type RouteKey =
| 'chat'
| 'tools'
| 'data'
| 'model-providers'
| 'display-settings'
| 'mcp'
// 状态管理
const modelStore = useModelStore()
// 响应式数据
const currentRoute = ref<RouteKey>('chat')
const isDark = ref(false)
const currentThemeColor = ref('#18a058')
const sidebarCollapsed = ref(false)
// 加载主题颜色设置
const loadThemeColor = () => {
try {
const saved = localStorage.getItem('cherry-display-settings')
if (saved) {
const settings = JSON.parse(saved)
currentThemeColor.value = settings.primaryColor || '#18a058'
}
} catch (error) {
console.error('获取主题颜色失败:', error)
}
}
// 计算属性
const theme = computed<GlobalTheme | null>(() => {
return isDark.value ? darkTheme : null
})
const themeOverrides = computed(() => {
const primaryColor = currentThemeColor.value
return {
common: {
primaryColor: primaryColor,
primaryColorHover: primaryColor + 'CC',
primaryColorPressed: primaryColor + '99',
primaryColorSuppl: primaryColor
}
}
})
// 方法
const toggleTheme = () => {
isDark.value = !isDark.value
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light')
}
const toggleSidebar = () => {
sidebarCollapsed.value = !sidebarCollapsed.value
}
// 生命周期
onMounted(() => {
// 初始化模型服务状态
modelStore.initialize()
// 加载主题颜色
loadThemeColor()
// 监听主题颜色变化
window.addEventListener('theme-color-changed', (event: any) => {
currentThemeColor.value = event.detail
})
})
</script>
<style scoped>
.app {
display: flex;
height: 100vh;
background: #f8fafc;
}
/* 侧边栏样式 */
.sidebar {
width: 280px;
background: #ffffff;
border-right: 1px solid #e2e8f0;
display: flex;
flex-direction: column;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);
transition: width 0.3s ease;
}
.sidebar.collapsed {
width: 64px;
}
.sidebar-header {
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar.collapsed .sidebar-header {
padding: 20px 12px;
flex-direction: column;
gap: 12px;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.app-logo {
display: flex;
align-items: center;
gap: 12px;
}
.app-title {
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.theme-toggle {
flex-shrink: 0;
}
.sidebar-content {
flex: 1;
padding: 16px 0;
}
.nav-section {
margin-bottom: 24px;
}
.section-title {
padding: 0 24px 12px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
color: #64748b;
letter-spacing: 0.5px;
}
.nav-items {
display: flex;
flex-direction: column;
gap: 2px;
}
.nav-item {
position: relative;
display: flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
margin: 0 12px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.sidebar.collapsed .nav-item {
padding: 12px;
margin: 0 6px;
justify-content: center;
}
.sidebar.collapsed .nav-item span {
display: none;
}
.nav-item:hover {
background: #f1f5f9;
color: #334155;
}
.nav-item.active {
background: #eff6ff;
color: #3b82f6;
font-weight: 600;
}
.nav-indicator {
position: absolute;
right: -12px;
width: 3px;
height: 20px;
background: #3b82f6;
border-radius: 2px;
}
.sidebar.collapsed .nav-indicator {
right: -6px;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
height: calc(100vh - 0px);
}
.content-panel {
flex: 1;
overflow-y: auto;
}
.content-page {
flex: 1;
padding: 32px;
overflow-y: auto;
background: #f8fafc;
}
.page-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid #e2e8f0;
}
.page-header h1 {
margin: 0;
font-size: 28px;
font-weight: 700;
color: #1e293b;
}
.page-header p {
margin: 4px 0 0 0;
color: #64748b;
font-size: 16px;
}
/* 内容网格布局 */
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.providers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.mcp-grid {
display: grid;
gap: 24px;
}
/* 卡片样式 */
.feature-card,
.action-card,
.tools-card,
.stats-card,
.provider-card,
.settings-card,
.mcp-card {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border: 1px solid #e2e8f0;
}
.feature-item {
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
color: #475569;
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
color: #475569;
}
/* 欢迎页面样式 */
.welcome-header {
text-align: center;
margin-bottom: 48px;
}
.welcome-logo {
margin-bottom: 24px;
}
.welcome-header h1 {
margin: 0 0 12px 0;
font-size: 36px;
font-weight: 700;
color: #1e293b;
}
.welcome-header p {
margin: 0;
font-size: 18px;
color: #64748b;
}
.welcome-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 24px;
margin-bottom: 48px;
}
.welcome-card {
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid #e2e8f0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.welcome-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-icon {
display: flex;
align-items: center;
justify-content: center;
height: 80px;
border-radius: 12px 12px 0 0;
}
.chat-icon {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: white;
}
.tools-icon {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: white;
}
.mcp-icon {
background: linear-gradient(135deg, #6366f1, #4f46e5);
color: white;
}
.welcome-card h3 {
margin: 16px 0 8px 0;
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.welcome-card p {
margin: 0;
color: #64748b;
font-size: 14px;
line-height: 1.6;
}
.stats-overview {
margin-top: 32px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 24px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.app {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
max-height: 300px;
}
.content-page {
padding: 20px;
}
.page-header {
margin-bottom: 24px;
padding-bottom: 16px;
}
.page-header h1 {
font-size: 24px;
}
.content-grid,
.providers-grid,
.settings-grid,
.welcome-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.welcome-header h1 {
font-size: 28px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 深色模式 */
[data-theme="dark"] .app {
background: #0f172a;
}
[data-theme="dark"] .sidebar {
background: #1e293b;
border-right-color: #334155;
}
[data-theme="dark"] .sidebar-header {
border-bottom-color: #334155;
}
[data-theme="dark"] .app-title {
color: #f8fafc;
}
[data-theme="dark"] .section-title {
color: #94a3b8;
}
[data-theme="dark"] .nav-item {
color: #94a3b8;
}
[data-theme="dark"] .nav-item:hover {
background: #334155;
color: #f1f5f9;
}
[data-theme="dark"] .nav-item.active {
background: rgba(59, 130, 246, 0.2);
color: #60a5fa;
}
[data-theme="dark"] .content-page {
background: #0f172a;
}
[data-theme="dark"] .page-header {
border-bottom-color: #334155;
}
[data-theme="dark"] .page-header h1 {
color: #f8fafc;
}
[data-theme="dark"] .page-header p {
color: #94a3b8;
}
[data-theme="dark"] .welcome-header h1 {
color: #f8fafc;
}
[data-theme="dark"] .welcome-header p {
color: #94a3b8;
}
[data-theme="dark"] .welcome-card h3 {
color: #f8fafc;
}
[data-theme="dark"] .welcome-card p {
color: #94a3b8;
}
[data-theme="dark"] .feature-item {
color: #cbd5e1;
}
[data-theme="dark"] .setting-item {
color: #cbd5e1;
}
</style>