feat: add lightweight admin workspace
This commit is contained in:
@@ -195,6 +195,10 @@ web/apps/web-ele/src/views/online-dev/*
|
||||
!web/apps/web-ele/src/views/online-dev/page-render/
|
||||
!web/apps/web-ele/src/views/online-dev/page-render/**
|
||||
web/apps/web-ele/src/views/dashboard/
|
||||
!web/apps/web-ele/src/views/dashboard/
|
||||
web/apps/web-ele/src/views/dashboard/*
|
||||
!web/apps/web-ele/src/views/dashboard/workspace/
|
||||
!web/apps/web-ele/src/views/dashboard/workspace/**
|
||||
web/apps/web-ele/src/views/demos/
|
||||
web/apps/web-ele/src/views/zq-smart-table/
|
||||
web/apps/web-ele/src/api/online-dev/
|
||||
|
||||
@@ -17,7 +17,7 @@ from core.menu.schema import MenuCreate, MenuUpdate
|
||||
menu_cache = CacheManager(prefix="menu:")
|
||||
|
||||
# 缓存key
|
||||
AI_AGENT_ADMIN_MENU_CACHE_VERSION = "v11"
|
||||
AI_AGENT_ADMIN_MENU_CACHE_VERSION = "v12"
|
||||
MENU_TREE_CACHE_KEY = f"tree:ai_agent_admin:{AI_AGENT_ADMIN_MENU_CACHE_VERSION}"
|
||||
USER_ROUTE_CACHE_PREFIX = f"user_route:ai_agent_admin:{AI_AGENT_ADMIN_MENU_CACHE_VERSION}:"
|
||||
|
||||
@@ -51,8 +51,8 @@ def _filter_ai_agent_admin_menus(menus: List[Menu]) -> List[Menu]:
|
||||
def _normalize_ai_agent_admin_menu(menu: Menu) -> None:
|
||||
"""Keep the retained control center on the lightweight admin dashboard."""
|
||||
if menu.name == "ControlCenter":
|
||||
menu.path = "/page-render/main_home"
|
||||
menu.component = "_core/page-render/index"
|
||||
menu.path = "/dashboard/workspace"
|
||||
menu.component = "/dashboard/workspace/index"
|
||||
menu.query = None
|
||||
|
||||
|
||||
|
||||
@@ -7038,9 +7038,9 @@
|
||||
"name": "ControlCenter",
|
||||
"title": "menu-title.controlCenter",
|
||||
"authCode": null,
|
||||
"path": "/ai-platform/agent",
|
||||
"path": "/dashboard/workspace",
|
||||
"type": "menu",
|
||||
"component": "/ai-platform/agent/index",
|
||||
"component": "/dashboard/workspace/index",
|
||||
"redirect": null,
|
||||
"activePath": null,
|
||||
"query": null,
|
||||
@@ -9386,7 +9386,7 @@
|
||||
"fields": {
|
||||
"application_id": "23CFVEUHdZcOQyku1mbIO",
|
||||
"config_key": "frontend_preferences",
|
||||
"config_value": "{\"app\":{\"defaultHomePath\":\"/ai-platform/agent\"}}",
|
||||
"config_value": "{\"app\":{\"defaultHomePath\":\"/dashboard/workspace\"}}",
|
||||
"config_type": "preferences",
|
||||
"description": "子应用UI偏好配置",
|
||||
"status": true,
|
||||
@@ -9406,7 +9406,7 @@
|
||||
"fields": {
|
||||
"application_id": "uRFPsrZ0BSJodp-qesFq6",
|
||||
"config_key": "frontend_preferences",
|
||||
"config_value": "{\"app\":{\"defaultHomePath\":\"/ai-platform/agent\"}}",
|
||||
"config_value": "{\"app\":{\"defaultHomePath\":\"/dashboard/workspace\"}}",
|
||||
"config_type": "preferences",
|
||||
"description": "子应用UI偏好配置",
|
||||
"status": true,
|
||||
@@ -12988,7 +12988,7 @@
|
||||
"cover": "",
|
||||
"app_type": "ai",
|
||||
"status": "published",
|
||||
"home_path": "/ai-platform/agent",
|
||||
"home_path": "/dashboard/workspace",
|
||||
"version": 1,
|
||||
"config": {},
|
||||
"owner_id": null,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"home": "首页",
|
||||
"controlCenter": "控制中心",
|
||||
"workspace": "工作台",
|
||||
"aiPlatform": "AI 平台",
|
||||
"aiAgent": "智能体",
|
||||
"workflowOrchestration": "流程编排",
|
||||
|
||||
@@ -16,7 +16,7 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
enablePreferences: false,
|
||||
accessMode: 'mixed',
|
||||
authPageLayout: 'panel-center',
|
||||
defaultHomePath: '/ai-platform/agent',
|
||||
defaultHomePath: '/dashboard/workspace',
|
||||
layout: 'header-sidebar-nav',
|
||||
preferencesButtonPosition: 'header',
|
||||
},
|
||||
|
||||
@@ -179,9 +179,12 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
'../views/_core/fallback/**/*.vue',
|
||||
'../views/_core/file-preview/index.vue',
|
||||
'../views/_core/menu/index.vue',
|
||||
'../views/_core/message/index.vue',
|
||||
'../views/_core/page-render/index.vue',
|
||||
'../views/_core/permission/index.vue',
|
||||
'../views/_core/role/index.vue',
|
||||
'../views/_core/system-config/index.vue',
|
||||
'../views/_core/ui-config/index.vue',
|
||||
'../views/_core/user/index.vue',
|
||||
'../views/ai-platform/agent/editor/index.vue',
|
||||
'../views/ai-platform/agent/index.vue',
|
||||
@@ -191,6 +194,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
'../views/ai-platform/workflow/editor/index.vue',
|
||||
'../views/ai-platform/workflow/index.vue',
|
||||
'../views/ai-platform/workflow-runs/index.vue',
|
||||
'../views/dashboard/workspace/index.vue',
|
||||
]);
|
||||
|
||||
const layoutMap: ComponentRecordType = {
|
||||
|
||||
@@ -206,7 +206,7 @@ function setupAccessGuard(router: Router) {
|
||||
|
||||
if (to.path === subAppRootPath) {
|
||||
const defaultHome =
|
||||
preferences.app.defaultHomePath || '/ai-platform/agent';
|
||||
preferences.app.defaultHomePath || '/dashboard/workspace';
|
||||
// 确保路径包含子应用前缀
|
||||
const subAppTargetPath = defaultHome.startsWith(subAppRootPath)
|
||||
? defaultHome
|
||||
@@ -222,7 +222,7 @@ function setupAccessGuard(router: Router) {
|
||||
const redirectPath = normalizeHomePath(
|
||||
(from.query.redirect ??
|
||||
(to.path === preferences.app.defaultHomePath
|
||||
? userInfo.homePath
|
||||
? preferences.app.defaultHomePath
|
||||
: to.fullPath)) as string,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const LIGHT_MENU_NAMES = new Set([
|
||||
'ControlCenter',
|
||||
'Dashboard',
|
||||
'DashboardWorkspace',
|
||||
'AIAgent',
|
||||
'AIKnowledgeDetail',
|
||||
'AIModelConfig',
|
||||
@@ -8,15 +11,21 @@ export const LIGHT_MENU_NAMES = new Set([
|
||||
'AnnouncementList',
|
||||
'AnnouncementManage',
|
||||
'CodexAgentChat',
|
||||
'MessageCenter',
|
||||
'MessageList',
|
||||
'KnowledgeBase',
|
||||
'SystemManagement',
|
||||
'SystemConfig',
|
||||
'SystemMenu',
|
||||
'SystemPermission',
|
||||
'SystemRole',
|
||||
'UIConfig',
|
||||
'UserManagement',
|
||||
]);
|
||||
|
||||
const LIGHT_ROUTE_PATH_PREFIXES = [
|
||||
'/control-center',
|
||||
'/dashboard',
|
||||
'/ai-platform/agent',
|
||||
'/ai-platform/knowledge',
|
||||
'/ai-platform/knowledge-base',
|
||||
@@ -24,10 +33,15 @@ const LIGHT_ROUTE_PATH_PREFIXES = [
|
||||
'/ai-platform/workflow',
|
||||
'/ai-platform/workflow-runs',
|
||||
'/agent-chat',
|
||||
'/message/list',
|
||||
'/message/announcement',
|
||||
'/message/announcement-list',
|
||||
'/page-render/main_home',
|
||||
'/system/config',
|
||||
'/system/menu',
|
||||
'/system/new-permission',
|
||||
'/system/role',
|
||||
'/system/ui-config',
|
||||
'/system/user',
|
||||
];
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
|
||||
|
||||
import { coreRoutes, fallbackNotFoundRoute } from './core';
|
||||
import aiPlatformRoutes from './modules/ai-platform';
|
||||
import dashboardRoutes from './modules/dashboard';
|
||||
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||
@@ -12,6 +13,7 @@ import aiPlatformRoutes from './modules/ai-platform';
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules({
|
||||
'./modules/ai-platform.ts': { default: aiPlatformRoutes },
|
||||
'./modules/dashboard.ts': { default: dashboardRoutes },
|
||||
});
|
||||
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||
|
||||
@@ -5,7 +5,7 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: { hideInMenu: true },
|
||||
name: 'ControlCenterLegacyRedirect',
|
||||
path: '/control-center',
|
||||
redirect: '/ai-platform/agent',
|
||||
redirect: '/dashboard/workspace',
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
@@ -14,7 +14,7 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
name: 'PageRenderMainHome',
|
||||
path: '/page-render/main_home',
|
||||
redirect: '/ai-platform/agent',
|
||||
redirect: '/dashboard/workspace',
|
||||
},
|
||||
{
|
||||
meta: { hideInMenu: true },
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
icon: 'lucide:layout-dashboard',
|
||||
order: 0,
|
||||
title: 'menu-title.controlCenter',
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
redirect: '/dashboard/workspace',
|
||||
children: [
|
||||
{
|
||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
title: 'menu-title.workspace',
|
||||
},
|
||||
name: 'DashboardWorkspace',
|
||||
path: '/dashboard/workspace',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -53,7 +53,7 @@ const appDynamicTitle = ref(true);
|
||||
const appWatermark = ref(false);
|
||||
const appWatermarkContent = ref('');
|
||||
const appEnableCheckUpdates = ref(true);
|
||||
const appDefaultHomePath = ref('/ai-platform/agent');
|
||||
const appDefaultHomePath = ref('/dashboard/workspace');
|
||||
const appEnablePreferences = ref(true);
|
||||
|
||||
const footerEnable = ref(true);
|
||||
@@ -106,7 +106,7 @@ function getDefaultConfig() {
|
||||
defaultHomePath:
|
||||
overrides.app?.defaultHomePath ||
|
||||
preferences.app.defaultHomePath ||
|
||||
'/ai-platform/agent',
|
||||
'/dashboard/workspace',
|
||||
enablePreferences: overrides.app?.enablePreferences ?? true,
|
||||
},
|
||||
footer: {
|
||||
@@ -229,7 +229,7 @@ async function loadConfig() {
|
||||
|
||||
// 获取带子应用前缀的首页路径
|
||||
function getDefaultHomePathWithPrefix(): string {
|
||||
const path = appDefaultHomePath.value || '/ai-platform/agent';
|
||||
const path = appDefaultHomePath.value || '/dashboard/workspace';
|
||||
const appCode = appContextStore.appCode;
|
||||
// 如果是子应用模式且路径不包含子应用前缀,自动添加
|
||||
if (appCode && !path.startsWith(`/app/${appCode}`)) {
|
||||
|
||||
@@ -0,0 +1,534 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui/es/page';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import {
|
||||
Bell,
|
||||
Bot,
|
||||
Brain,
|
||||
GitFork,
|
||||
KeyRound,
|
||||
Megaphone,
|
||||
MessageSquare,
|
||||
Settings,
|
||||
Shield,
|
||||
Users,
|
||||
Workflow,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { ElButton, ElCard, ElTag } from 'element-plus';
|
||||
|
||||
defineOptions({ name: 'DashboardWorkspace' });
|
||||
|
||||
type Accent = 'blue' | 'cyan' | 'green' | 'orange' | 'violet';
|
||||
|
||||
interface MetricItem {
|
||||
accent: Accent;
|
||||
label: string;
|
||||
title: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
accent: Accent;
|
||||
description: string;
|
||||
icon: any;
|
||||
path: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const displayName = computed(
|
||||
() => userStore.userInfo?.realName || userStore.userInfo?.username || '管理员',
|
||||
);
|
||||
|
||||
const metrics: MetricItem[] = [
|
||||
{
|
||||
accent: 'blue',
|
||||
label: '协作入口',
|
||||
title: '智能体',
|
||||
value: 'Agent',
|
||||
},
|
||||
{
|
||||
accent: 'cyan',
|
||||
label: '运行闭环',
|
||||
title: '流程编排',
|
||||
value: 'Flow',
|
||||
},
|
||||
{
|
||||
accent: 'green',
|
||||
label: '基础治理',
|
||||
title: '权限与角色',
|
||||
value: 'Auth',
|
||||
},
|
||||
{
|
||||
accent: 'orange',
|
||||
label: '运营触达',
|
||||
title: '公告消息',
|
||||
value: 'Notice',
|
||||
},
|
||||
];
|
||||
|
||||
const aiNavItems: NavItem[] = [
|
||||
{
|
||||
accent: 'blue',
|
||||
description: '创建、发布和调试业务智能体',
|
||||
icon: Bot,
|
||||
path: '/ai-platform/agent',
|
||||
title: '智能体管理',
|
||||
},
|
||||
{
|
||||
accent: 'cyan',
|
||||
description: '编排多智能体、LLM 和工具节点',
|
||||
icon: Workflow,
|
||||
path: '/ai-platform/workflow',
|
||||
title: '流程编排',
|
||||
},
|
||||
{
|
||||
accent: 'violet',
|
||||
description: '查看运行事件、节点状态和失败原因',
|
||||
icon: GitFork,
|
||||
path: '/ai-platform/workflow-runs',
|
||||
title: '执行历史',
|
||||
},
|
||||
{
|
||||
accent: 'green',
|
||||
description: '维护可用模型和默认 Chat 模型',
|
||||
icon: Brain,
|
||||
path: '/ai-platform/model',
|
||||
title: '模型配置',
|
||||
},
|
||||
];
|
||||
|
||||
const adminNavItems: NavItem[] = [
|
||||
{
|
||||
accent: 'green',
|
||||
description: '维护后台账号、状态和基础资料',
|
||||
icon: Users,
|
||||
path: '/system/user',
|
||||
title: '用户管理',
|
||||
},
|
||||
{
|
||||
accent: 'violet',
|
||||
description: '管理角色权限和资源范围',
|
||||
icon: Shield,
|
||||
path: '/system/role',
|
||||
title: '角色权限',
|
||||
},
|
||||
{
|
||||
accent: 'blue',
|
||||
description: '维护菜单、路由和页面入口',
|
||||
icon: KeyRound,
|
||||
path: '/system/menu',
|
||||
title: '菜单管理',
|
||||
},
|
||||
{
|
||||
accent: 'orange',
|
||||
description: '发布公告并查看阅读反馈',
|
||||
icon: Megaphone,
|
||||
path: '/message/announcement',
|
||||
title: '公告管理',
|
||||
},
|
||||
];
|
||||
|
||||
const runtimeItems = [
|
||||
{
|
||||
description: '聊天、流程运行和系统通知会集中进入消息中心。',
|
||||
icon: MessageSquare,
|
||||
path: '/message/list',
|
||||
title: '消息中心',
|
||||
},
|
||||
{
|
||||
description: '检查最新公告和未读提醒。',
|
||||
icon: Bell,
|
||||
path: '/message/announcement-list',
|
||||
title: '公告列表',
|
||||
},
|
||||
{
|
||||
description: '调整系统配置、模型参数和界面偏好。',
|
||||
icon: Settings,
|
||||
path: '/system/config',
|
||||
title: '系统配置',
|
||||
},
|
||||
];
|
||||
|
||||
function go(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<div class="workspace-page">
|
||||
<section class="workspace-hero">
|
||||
<div class="min-w-0">
|
||||
<div class="hero-kicker">AI Agent Admin</div>
|
||||
<h1>你好,{{ displayName }}</h1>
|
||||
<p>
|
||||
当前后台保留基础治理和 AI 协作主链路,优先处理智能体、流程编排、运行观测和权限管理。
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<ElButton type="primary" @click="go('/ai-platform/agent')">
|
||||
<Bot class="mr-1 size-4" />
|
||||
智能体
|
||||
</ElButton>
|
||||
<ElButton @click="go('/ai-platform/workflow')">
|
||||
<Workflow class="mr-1 size-4" />
|
||||
流程编排
|
||||
</ElButton>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="metric-grid">
|
||||
<ElCard
|
||||
v-for="item in metrics"
|
||||
:key="item.title"
|
||||
class="metric-card"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="metric-top">
|
||||
<span :class="['accent-dot', `accent-${item.accent}`]"></span>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
<div class="metric-value">{{ item.value }}</div>
|
||||
<div class="metric-title">{{ item.title }}</div>
|
||||
</ElCard>
|
||||
</section>
|
||||
|
||||
<section class="content-grid">
|
||||
<ElCard class="panel-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<span>AI 协作</span>
|
||||
<ElTag effect="plain" size="small">核心链路</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="nav-grid">
|
||||
<button
|
||||
v-for="item in aiNavItems"
|
||||
:key="item.path"
|
||||
class="nav-tile"
|
||||
type="button"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<span :class="['tile-icon', `tile-${item.accent}`]">
|
||||
<component :is="item.icon" class="size-5" />
|
||||
</span>
|
||||
<span class="tile-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<ElCard class="panel-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<span>基础后台</span>
|
||||
<ElTag effect="plain" size="small" type="success">已保留</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="nav-grid">
|
||||
<button
|
||||
v-for="item in adminNavItems"
|
||||
:key="item.path"
|
||||
class="nav-tile"
|
||||
type="button"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<span :class="['tile-icon', `tile-${item.accent}`]">
|
||||
<component :is="item.icon" class="size-5" />
|
||||
</span>
|
||||
<span class="tile-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ElCard>
|
||||
</section>
|
||||
|
||||
<ElCard class="panel-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<span>运维与消息</span>
|
||||
<ElTag effect="plain" size="small" type="warning">轻量入口</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="runtime-list">
|
||||
<button
|
||||
v-for="item in runtimeItems"
|
||||
:key="item.path"
|
||||
class="runtime-row"
|
||||
type="button"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<span class="runtime-icon">
|
||||
<component :is="item.icon" class="size-4" />
|
||||
</span>
|
||||
<span class="runtime-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<span>{{ item.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.workspace-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.workspace-hero {
|
||||
align-items: center;
|
||||
background:
|
||||
linear-gradient(135deg, rgb(36 99 235 / 8%), transparent 42%),
|
||||
var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: space-between;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.workspace-hero h1 {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.workspace-hero p {
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 1.7;
|
||||
margin: 8px 0 0;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.metric-card,
|
||||
.panel-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.metric-card :deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.metric-top {
|
||||
align-items: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.accent-dot {
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.accent-blue {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.accent-cyan {
|
||||
background: var(--el-color-info);
|
||||
}
|
||||
|
||||
.accent-green {
|
||||
background: var(--el-color-success);
|
||||
}
|
||||
|
||||
.accent-orange {
|
||||
background: var(--el-color-warning);
|
||||
}
|
||||
|
||||
.accent-violet {
|
||||
background: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.metric-title {
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
align-items: center;
|
||||
color: var(--el-text-color-primary);
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nav-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.nav-tile,
|
||||
.runtime-row {
|
||||
align-items: center;
|
||||
background: var(--el-bg-color-page);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
padding: 14px;
|
||||
text-align: left;
|
||||
transition:
|
||||
border-color 180ms ease,
|
||||
box-shadow 180ms ease,
|
||||
transform 180ms ease;
|
||||
}
|
||||
|
||||
.nav-tile:hover,
|
||||
.runtime-row:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 8px 24px rgb(15 23 42 / 8%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.tile-icon,
|
||||
.runtime-icon {
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
height: 38px;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.tile-blue {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.tile-cyan {
|
||||
background: var(--el-color-info-light-9);
|
||||
color: var(--el-color-info);
|
||||
}
|
||||
|
||||
.tile-green {
|
||||
background: var(--el-color-success-light-9);
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.tile-orange {
|
||||
background: var(--el-color-warning-light-9);
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
|
||||
.tile-violet {
|
||||
background: var(--el-color-danger-light-9);
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.tile-copy,
|
||||
.runtime-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tile-copy strong,
|
||||
.runtime-copy strong {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.tile-copy span,
|
||||
.runtime-copy span {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.runtime-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.runtime-icon {
|
||||
background: var(--el-fill-color-light);
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.metric-grid,
|
||||
.runtime-list {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.workspace-hero,
|
||||
.hero-actions {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-grid,
|
||||
.metric-grid,
|
||||
.nav-grid,
|
||||
.runtime-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user