Files
c-api/capi-ui/src/views/sys/Dashboard.vue
2025-09-02 00:11:55 +08:00

915 lines
21 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>
<div class="app-container">
<a-layout style="min-height: 100%;">
<!-- 顶部导航栏使用渐变淡蓝色背景 -->
<a-layout-header class="header">
<!-- 左侧Logo和系统名称 - 增加了图标 -->
<div class="logo">
<img src="/my.png" class="logo-img" />
<span class="system-name">cApi </span>
</div>
<!-- 主导航菜单调整按钮大小和间距 -->
<a-menu
theme="light"
mode="horizontal"
:selectedKeys="selectedKey"
class="main-menu"
@click="handleMenuClick"
>
<a-menu-item key="app" class="menu-button">
<ApiOutlined class="menu-icon" />
<span>应用中心</span>
</a-menu-item>
<a-menu-item key="data" class="menu-button">
<DatabaseOutlined class="menu-icon" />
<span>数据中心</span>
</a-menu-item>
<a-menu-item key="integration" class="menu-button">
<LinkOutlined class="menu-icon" />
<span>集成中心</span>
</a-menu-item>
<a-menu-item key="system" class="menu-button">
<SettingOutlined class="menu-icon" />
<span>系统管理</span>
</a-menu-item>
</a-menu>
<!-- 右侧功能区搜索框图标和用户 -->
<div class="header-actions">
<!-- 搜索框 -->
<div class="search-box">
<SearchOutlined class="search-icon" />
<input type="text" placeholder="搜索" class="search-input" />
</div>
<!-- 功能图标区 -->
<div class="action-icons">
<a-tooltip title="收藏">
<StarOutlined class="action-icon" />
</a-tooltip>
<a-tooltip title="通知">
<BellOutlined class="action-icon" />
</a-tooltip>
<a-tooltip title="帮助">
<QuestionCircleOutlined class="action-icon" />
</a-tooltip>
<a-tooltip title="设置">
<SettingOutlined class="action-icon" />
</a-tooltip>
</div>
<!-- 用户头像 -->
<a-dropdown
placement="bottomRight"
:auto-adjust-overflow="true"
@visible-change="handleDropdownVisible"
>
<div class="user-avatar">
<img src="https://picsum.photos/id/1005/200/200" alt="用户头像" class="avatar-img" />
</div>
<template #overlay>
<a-menu class="user-menu" @click="handleUserMenuClick">
<a-menu-item key="profile">个人资料</a-menu-item>
<a-menu-item key="settings">账户设置</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout" class="logout-menu-item">
<LogoutOutlined class="mr-2" /> 退出登录
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</a-layout-header>
<!-- 标签栏保持原有功能 -->
<div class="tab-bar">
<a-tag
:class="['console-tab', activeTabKey === 'console' ? 'console-tab--active' : '']"
@click="switchToConsole"
>
<HomeOutlined class="console-tab-icon" />
<span>控制台</span>
</a-tag>
<a-button
size="small"
class="tab-nav-btn"
@click="switchTab('prev')"
:disabled="currentTabIndex === 0 || otherTabs.length === 0"
ghost
>
&lt;
</a-button>
<a-tabs
v-model:activeKey="activeTabKey"
@change="handleTabChange"
class="google-tabs"
:animated="false"
hide-add
>
<a-tab-pane
v-for="tab in otherTabs"
:key="tab.key"
:tab="renderTabTitle(tab)"
/>
</a-tabs>
<a-button
size="small"
class="tab-nav-btn"
@click="switchTab('next')"
:disabled="currentTabIndex === otherTabs.length - 1 || otherTabs.length === 0"
ghost
>
&gt;
</a-button>
<a-button
size="small"
class="close-all-btn"
@click="closeAllTabs"
:disabled="otherTabs.length === 0"
ghost
>
<CloseOutlined class="close-icon" /> 关闭所有
</a-button>
</div>
<!-- 内容区域保持原有功能 -->
<a-layout-content class="content">
<div class="home-content">
<component
:is="currentMainComponent"
:key="activeTabKey"
/>
</div>
</a-layout-content>
</a-layout>
<!-- 模块菜单弹窗保持原有功能 -->
<a-modal
v-model:open="moduleModalVisible"
:title="currentModuleName"
:width="`80%`"
:centered="true"
:mask-closable="false"
@cancel="moduleModalVisible = false"
wrap-class-name="module-modal-wrapper"
>
<div class="module-menu-container">
<a-row :gutter="[{ xs: 16, sm: 24 }, { xs: 16, sm: 24 }]">
<a-col
v-for="menu in currentModuleMenus"
:key="menu.key"
:xs="12"
:sm="8"
:md="6"
:lg="4"
:xl="3"
>
<a-card
class="menu-card"
@click="handleMenuCardClick(menu)"
hoverable
bordered
>
<div class="menu-card-inner">
<component :is="menu.icon" class="menu-icon" />
<div class="menu-title">{{ menu.title }}</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
</a-modal>
<!-- 退出确认对话框保持原有功能 -->
<a-modal
v-model:open="logoutVisible"
:centered="true"
:closable="false"
:mask-closable="false"
:footer="null"
:width="420"
wrap-class-name="logout-modal-wrapper"
>
<div class="logout-body">
<p class="logout-title">
<ExclamationCircleOutlined style="color:#faad14; margin-right:8px;" />
温馨提示
</p>
<p class="logout-desc">是否确认退出系统</p>
<div class="logout-actions">
<a-button size="large" @click="logoutVisible = false">取消</a-button>
<a-button
type="primary"
size="large"
style="margin-left: 16px"
@click="doLogout"
>
确定
</a-button>
</div>
</div>
</a-modal>
</div>
</template>
<script setup>
import { ref, computed, h } from 'vue';
import { Layout, Menu, Button, Tabs, Card, Tag, Dropdown, Modal, Row, Col, Tooltip } from 'ant-design-vue';
import {
ApiOutlined,
UserOutlined,
CloseOutlined,
LinkOutlined,
LogoutOutlined,
ExclamationCircleOutlined,
BarChartOutlined,
DownloadOutlined,
SettingOutlined,
FormOutlined,
HistoryOutlined,
UserAddOutlined,
HomeOutlined,
DatabaseOutlined, // 用于系统名称前的图标
SearchOutlined,
StarOutlined,
BellOutlined,
QuestionCircleOutlined
} from '@ant-design/icons-vue';
// 导入Main组件控制台要展示的内容
import Main from './main.vue';
// 模拟业务组件
const MockComponent = (props) => {
return h('div', { style: { padding: '20px' } }, [
h('h3', `这是${props.title}页面`),
h('p', '此处将展示该功能模块的具体内容')
]);
};
// 模块与菜单映射配置
const moduleMenusConfig = {
app: {
name: '应用中心',
menus: [
{
key: 'app-list',
title: '应用列表',
icon: ApiOutlined,
component: () => h(MockComponent, { title: '应用列表' }),
closable: true
},
{
key: 'app-create',
title: '创建应用',
icon: FormOutlined,
component: () => h(MockComponent, { title: '创建应用' }),
closable: true
}
]
},
data: {
name: '数据中心',
menus: [
{
key: 'data-analytics',
title: '数据分析',
icon: BarChartOutlined,
component: () => h(MockComponent, { title: '数据分析' }),
closable: true
},
{
key: 'data-export',
title: '数据导出',
icon: DownloadOutlined,
component: () => h(MockComponent, { title: '数据导出' }),
closable: true
}
]
},
integration: {
name: '集成中心',
menus: [
{
key: 'api-list',
title: 'API列表',
icon: ApiOutlined,
component: () => h(MockComponent, { title: 'API列表' }),
closable: true
},
{
key: 'integration-history',
title: '集成记录',
icon: HistoryOutlined,
component: () => h(MockComponent, { title: '集成记录' }),
closable: true
}
]
},
system: {
name: '系统管理',
menus: [
{
key: 'system-settings',
title: '系统设置',
icon: SettingOutlined,
component: () => h(MockComponent, { title: '系统设置' }),
closable: true
},
{
key: 'user-manage',
title: '用户管理',
icon: UserOutlined,
component: () => h(MockComponent, { title: '用户管理' }),
closable: true
}
]
}
};
// 状态管理
const selectedKey = ref(['console']);
const moduleModalVisible = ref(false);
const currentModuleKey = ref('console');
const activeTabKey = ref('console');
// 标签页数据
const allTabs = ref([
{
key: 'console',
title: '控制台',
component: Main,
closable: false
}
]);
// 计算属性
const otherTabs = computed(() =>
allTabs.value.filter(tab => tab.key !== 'console')
);
const currentTabIndex = computed(() =>
otherTabs.value.findIndex(tab => tab.key === activeTabKey.value)
);
const currentModuleName = computed(() =>
moduleMenusConfig[currentModuleKey.value]?.name || '功能菜单'
);
const currentModuleMenus = computed(() =>
moduleMenusConfig[currentModuleKey.value]?.menus || []
);
const currentMainComponent = computed(() => {
try {
const targetTab = allTabs.value.find(tab => tab.key === activeTabKey.value);
return targetTab ? targetTab.component : null;
} catch (error) {
console.error('组件加载失败:', error);
return null;
}
});
// 事件处理
const renderTabTitle = (tab) => {
return h('div', { class: 'tab-title-container' }, [
h('span', { class: 'tab-title-text' }, tab.title),
tab.closable && h(
'span',
{
class: 'tab-close-btn',
onClick: (e) => {
e.stopPropagation();
deleteTab(tab.key);
}
},
h(CloseOutlined, { size: 12 })
)
]);
};
const switchToConsole = () => {
activeTabKey.value = 'console';
};
const switchTab = (direction) => {
if (direction === 'prev' && currentTabIndex.value > 0) {
activeTabKey.value = otherTabs.value[currentTabIndex.value - 1].key;
} else if (
direction === 'next' &&
currentTabIndex.value < otherTabs.value.length - 1
) {
activeTabKey.value = otherTabs.value[currentTabIndex.value + 1].key;
}
};
const deleteTab = (key) => {
if (key === 'console') return;
const tabIndex = allTabs.value.findIndex(tab => tab.key === key);
if (tabIndex === -1) return;
allTabs.value = allTabs.value.filter(tab => tab.key !== key);
if (key === activeTabKey.value) {
const targetTab = allTabs.value[tabIndex - 1] || allTabs.value.find(tab => tab.key === 'console');
activeTabKey.value = targetTab.key;
}
};
const closeAllTabs = () => {
allTabs.value = allTabs.value.filter(tab => tab.key === 'console');
activeTabKey.value = 'console';
};
const handleTabChange = (key) => {
activeTabKey.value = key;
// 根据标签页映射到对应的模块
const tabToModuleMap = {
'dashboard': 'console',
'tasks': 'console',
'app-list': 'app',
'app-create': 'app',
'data-analytics': 'data',
'data-export': 'data',
'api-list': 'integration',
'integration-history': 'integration',
'system-settings': 'system',
'user-manage': 'system'
};
selectedKey.value = [tabToModuleMap[key] || 'console'];
};
const handleMenuClick = ({ key }) => {
currentModuleKey.value = key;
moduleModalVisible.value = true;
selectedKey.value = [key];
};
const handleMenuCardClick = (menuItem) => {
moduleModalVisible.value = false;
const isTabExists = allTabs.value.some(tab => tab.key === menuItem.key);
if (!isTabExists) {
allTabs.value.push({
key: menuItem.key,
title: menuItem.title,
component: menuItem.component,
closable: menuItem.closable
});
}
activeTabKey.value = menuItem.key;
};
const handleDropdownVisible = (visible) => {
console.log('下拉菜单状态:', visible ? '显示' : '隐藏');
};
const handleUserMenuClick = ({ key }) => {
if (key === 'logout') {
handleLogout();
} else {
console.log('用户菜单点击:', key);
}
};
const logoutVisible = ref(false);
const handleLogout = () => {
logoutVisible.value = true;
};
const doLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
logoutVisible.value = false;
alert('退出登录成功,即将跳转到登录页');
// 实际项目中应该使用路由跳转
// router.push('/login');
};
</script>
<style scoped>
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body,
.app-container {
height: 100vh;
overflow: hidden;
}
.app-container {
display: flex;
flex-direction: column;
}
/* 顶部导航:使用渐变淡蓝色背景 */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
height: 64px;
width: 100%;
background: linear-gradient(90deg, #e6f7ff 0%, #f0f9ff 100%);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
/* Logo和系统名称 - 添加了图标样式 */
.logo {
display: flex;
align-items: center;
padding-right: 20px;
border-right: 1px solid rgba(24, 144, 255, 0.1);
gap: 10px; /* 图标与文字间距 */
}
.logo-icon {
color: #1890ff;
font-size: 22px;
}
.logo-img {
width: 65px; /* 与文字高度匹配,可根据图片比例调整 */
height: 65px;
object-fit: contain; /* 关键:保持图片比例,避免拉伸变形 */
vertical-align: middle; /* 与文字垂直居中对齐 */
}
.system-name {
font-size: 18px;
font-weight: 600;
color: #1890ff;
}
/* 主导航菜单:优化按钮样式 */
.main-menu {
flex: 1;
margin: 0 25px;
justify-content: flex-start;
}
:deep(.ant-menu-horizontal) {
background: transparent !important;
border-bottom: none !important;
}
/* 调整按钮大小和间距 */
.menu-button {
border-radius: 4px;
margin: 110 120px !important;
transition: all 0.3s ease;
margin-top: 12px;
}
:deep(.ant-menu-item) {
color: #096dd9 !important; /* 加深文字颜色,增强对比 */
font-size: 13px; /* 缩小字体 */
padding: 0 12px !important; /* 减小内边距 */
height: 36px !important; /* 减小按钮高度 */
line-height: 36px !important;
background-color: rgba(255, 255, 255, 0.8) !important; /* 调整背景透明度 */
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
:deep(.ant-menu-item-selected) {
background-color: #fff !important;
color: #096dd9 !important;
box-shadow: 0 2px 6px rgba(24, 144, 255, 0.2);
}
:deep(.ant-menu-item:hover:not(.ant-menu-item-selected)) {
background-color: #fff !important;
color: #096dd9 !important;
transform: translateY(-1px);
box-shadow: 0 2px 5px rgba(24, 144, 255, 0.15);
}
.menu-icon {
margin-right: 6px; /* 减小图标与文字间距 */
font-size: 14px; /* 缩小图标 */
}
/* 右侧功能区 */
.header-actions {
display: flex;
align-items: center;
gap: 16px;
}
/* 搜索框 */
.search-box {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.8);
border-radius: 4px;
padding: 0 12px;
height: 36px;
width: 240px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.search-icon {
color: #8c8c8c;
margin-right: 8px;
}
.search-input {
background: transparent;
border: none;
outline: none;
color: #333;
width: 100%;
font-size: 14px;
}
.search-input::placeholder {
color: #b3b3b3;
}
/* 功能图标区 */
.action-icons {
display: flex;
align-items: center;
gap: 16px;
padding-right: 16px;
border-right: 1px solid rgba(24, 144, 255, 0.1);
}
.action-icon {
color: #1890ff;
font-size: 18px;
cursor: pointer;
transition: all 0.2s;
}
.action-icon:hover {
color: #096dd9;
transform: scale(1.1);
}
/* 用户头像 */
.user-avatar {
cursor: pointer;
}
.avatar-img {
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
border: 2px solid transparent;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: all 0.2s;
}
.user-avatar:hover .avatar-img {
border-color: #1890ff;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.2);
}
/* 用户菜单 */
.user-menu {
width: 160px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.logout-menu-item {
color: #f5222d !important;
}
/* 标签栏:保持原有样式 */
.tab-bar {
background: #fff;
border-bottom: 1px solid #e0e0e0;
padding: 0 20px;
height: 44px;
margin-top: 2px;
display: flex;
align-items: center;
gap: 8px;
width: 100%;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
transition: all 0.3s ease;
}
.console-tab {
height: 32px;
padding: 0 16px;
border-radius: 6px 6px 0 0;
border: none;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
color: #666;
background: transparent;
transition: all 0.2s ease;
}
.console-tab-icon {
margin-right: 6px;
font-size: 14px;
}
.console-tab--active {
background: #e8f4ff !important;
color: #1890ff;
font-weight: 500;
box-shadow: 0 1px 2px rgba(24, 144, 255, 0.1);
}
.console-tab:hover:not(.console-tab--active) {
background: #f5f9ff;
color: #1890ff;
}
/* 标签导航按钮 */
.tab-nav-btn {
width: 28px;
height: 28px;
padding: 0;
color: #666;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.tab-nav-btn:hover {
background: #f5f9ff;
color: #1890ff;
}
.tab-nav-btn:disabled {
color: #ccc !important;
cursor: not-allowed;
background: transparent !important;
}
/* 标签页样式 */
.google-tabs {
flex: 1;
overflow: hidden;
}
.ant-tabs-nav {
height: 100%;
margin: 0 !important;
}
.ant-tabs-nav-list {
height: 100%;
align-items: center;
}
.ant-tabs-tab {
height: 36px;
padding: 0 16px;
margin: 0 2px;
border-radius: 6px 6px 0 0;
background: transparent;
transition: all 0.2s ease;
font-size: 14px;
color: #666;
}
.ant-tabs-tab:hover:not(.ant-tabs-tab-active) {
background: #f5f9ff;
color: #1890ff;
}
.ant-tabs-tab.ant-tabs-tab-active {
background: #e8f4ff !important;
color: #1890ff !important;
font-weight: 500;
box-shadow: 0 1px 2px rgba(24, 144, 255, 0.1);
border-bottom: 2px solid #1890ff;
}
.ant-tabs-ink-bar {
display: none;
}
/* 标签标题容器 */
.tab-title-container {
display: flex;
align-items: center;
gap: 6px;
height: 100%;
padding: 0 4px;
}
.tab-title-text {
white-space: nowrap;
}
.tab-close-btn {
width: 16px;
height: 16px;
display: none;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #999;
cursor: pointer;
transition: all 0.2s;
}
.tab-title-container:hover .tab-close-btn {
display: inline-flex;
}
.ant-tabs-tab-active .tab-close-btn {
color: #1890ff;
}
.tab-close-btn:hover {
background: #d1eaff;
color: #1890ff;
}
/* 关闭所有按钮 */
.close-all-btn {
color: #666;
margin-left: 2px;
font-size: 13px;
display: flex;
align-items: center;
border-radius: 4px;
padding: 0 10px;
}
.close-all-btn:hover {
color: #1890ff;
background: #f5f9ff;
}
.close-all-btn:disabled {
color: #ccc !important;
cursor: not-allowed;
background: transparent !important;
}
.close-icon {
margin-right: 4px;
font-size: 12px;
}
/* 内容区域:保持原有样式 */
.content {
background: #f8f9fa;
padding: 16px;
flex-grow: 1;
overflow-y: auto;
width: 100%;
}
.home-content {
background: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
height: 100%;
overflow: auto;
}
/* 模块菜单弹窗:保持原有样式 */
.module-modal-wrapper .ant-modal-content {
border-radius: 8px;
}
.module-menu-container {
padding: 24px 0;
}
.menu-card {
height: 160px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
border-color: #e8e8e8;
}
.menu-card:hover {
border-color: #1890ff;
box-shadow: 0 4px 16px rgba(24, 144, 255, 0.15);
transform: translateY(-2px);
}
.menu-card-inner {
text-align: center;
}
.menu-icon {
font-size: 32px;
color: #1890ff;
margin-bottom: 16px;
transition: color 0.3s;
}
.menu-card:hover .menu-icon {
color: #096dd9;
}
.menu-title {
font-size: 16px;
color: #333;
font-weight: 500;
}
/* 退出对话框:保持原有样式 */
.logout-modal-wrapper .ant-modal {
width: 480px !important;
}
.logout-body {
text-align: center;
padding: 8px 0 4px;
}
.logout-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
text-align: left;
color: #333;
}
.logout-desc {
font-size: 16px;
color: #555;
margin-bottom: 32px;
text-align: left;
}
.logout-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
</style>