feat(admin): Complete Phase 3.5.1-3.5.4 Prompt Management System (83%)
Summary: - Implement Prompt management infrastructure and core services - Build admin portal frontend with light theme - Integrate CodeMirror 6 editor for non-technical users Phase 3.5.1: Infrastructure Setup - Create capability_schema for Prompt storage - Add prompt_templates and prompt_versions tables - Add prompt:view/edit/debug/publish permissions - Migrate RVW prompts to database (RVW_EDITORIAL, RVW_METHODOLOGY) Phase 3.5.2: PromptService Core - Implement gray preview logic (DRAFT for debuggers, ACTIVE for users) - Module-level debug control (setDebugMode) - Handlebars template rendering - Variable extraction and validation (extractVariables, validateVariables) - Three-level disaster recovery (database -> cache -> hardcoded fallback) Phase 3.5.3: Management API - 8 RESTful endpoints (/api/admin/prompts/*) - Permission control (PROMPT_ENGINEER can edit, SUPER_ADMIN can publish) Phase 3.5.4: Frontend Management UI - Build admin portal architecture (AdminLayout, OrgLayout) - Add route system (/admin/*, /org/*) - Implement PromptListPage (filter, search, debug switch) - Implement PromptEditor (CodeMirror 6 simplified for clinical users) - Implement PromptEditorPage (edit, save, publish, test, version history) Technical Details: - Backend: 6 files, ~2044 lines (prompt.service.ts 596 lines) - Frontend: 9 files, ~1735 lines (PromptEditorPage.tsx 399 lines) - CodeMirror 6: Line numbers, auto-wrap, variable highlight, search, undo/redo - Chinese-friendly: 15px font, 1.8 line-height, system fonts Next Step: Phase 3.5.5 - Integrate RVW module with PromptService Tested: Backend API tests passed (8/8), Frontend pending user testing Status: Ready for Phase 3.5.5 RVW integration
This commit is contained in:
236
frontend-v2/src/framework/layout/AdminLayout.tsx
Normal file
236
frontend-v2/src/framework/layout/AdminLayout.tsx
Normal file
@@ -0,0 +1,236 @@
|
||||
import { Suspense, useState } from 'react'
|
||||
import { Outlet, Navigate, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Spin, Layout, Menu, Avatar, Dropdown, Badge } from 'antd'
|
||||
import {
|
||||
DashboardOutlined,
|
||||
CodeOutlined,
|
||||
TeamOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
LogoutOutlined,
|
||||
SwapOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
BellOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { useAuth } from '../auth'
|
||||
import ErrorBoundary from '../modules/ErrorBoundary'
|
||||
|
||||
const { Header, Sider, Content } = Layout
|
||||
|
||||
// 运营管理端主色:翠绿
|
||||
const PRIMARY_COLOR = '#10b981'
|
||||
|
||||
/**
|
||||
* 运营管理端布局(方案A:全浅色)
|
||||
*
|
||||
* @description
|
||||
* - 白色侧边栏 + 翠绿强调色
|
||||
* - 浅灰内容区,信息清晰
|
||||
* - 权限检查:SUPER_ADMIN / PROMPT_ENGINEER
|
||||
*/
|
||||
const AdminLayout = () => {
|
||||
const { isAuthenticated, isLoading, user, logout } = useAuth()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
// 加载中
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-50">
|
||||
<Spin size="large" tip="加载中..." />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 未登录
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
// 权限检查:只有 SUPER_ADMIN 和 PROMPT_ENGINEER 可访问
|
||||
const allowedRoles = ['SUPER_ADMIN', 'PROMPT_ENGINEER']
|
||||
if (!allowedRoles.includes(user?.role || '')) {
|
||||
return (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">🚫</div>
|
||||
<h2 className="text-xl mb-2 text-gray-800">无权访问运营管理端</h2>
|
||||
<p className="text-gray-500 mb-4">需要 SUPER_ADMIN 或 PROMPT_ENGINEER 权限</p>
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="px-4 py-2 text-white rounded hover:opacity-90"
|
||||
style={{ background: PRIMARY_COLOR }}
|
||||
>
|
||||
返回首页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 侧边栏菜单
|
||||
const menuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: '/admin/dashboard',
|
||||
icon: <DashboardOutlined />,
|
||||
label: '运营概览',
|
||||
},
|
||||
{
|
||||
key: '/admin/prompts',
|
||||
icon: <CodeOutlined />,
|
||||
label: 'Prompt管理',
|
||||
},
|
||||
{
|
||||
key: '/admin/tenants',
|
||||
icon: <TeamOutlined />,
|
||||
label: '租户管理',
|
||||
},
|
||||
{
|
||||
key: '/admin/users',
|
||||
icon: <UserOutlined />,
|
||||
label: '用户管理',
|
||||
},
|
||||
{
|
||||
key: '/admin/system',
|
||||
icon: <SettingOutlined />,
|
||||
label: '系统配置',
|
||||
},
|
||||
]
|
||||
|
||||
// 用户下拉菜单
|
||||
const userMenuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'switch-app',
|
||||
icon: <SwapOutlined />,
|
||||
label: '切换到业务端',
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
label: '退出登录',
|
||||
danger: true,
|
||||
},
|
||||
]
|
||||
|
||||
const handleUserMenuClick = ({ key }: { key: string }) => {
|
||||
if (key === 'logout') {
|
||||
logout()
|
||||
navigate('/login')
|
||||
} else if (key === 'switch-app') {
|
||||
navigate('/')
|
||||
}
|
||||
}
|
||||
|
||||
const handleMenuClick = ({ key }: { key: string }) => {
|
||||
navigate(key)
|
||||
}
|
||||
|
||||
// 获取当前选中的菜单项
|
||||
const selectedKey = menuItems.find(item =>
|
||||
location.pathname.startsWith(item?.key as string)
|
||||
)?.key as string || '/admin/dashboard'
|
||||
|
||||
return (
|
||||
<Layout className="h-screen">
|
||||
{/* 侧边栏 - 白色 */}
|
||||
<Sider
|
||||
trigger={null}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
style={{ background: '#fff' }}
|
||||
width={220}
|
||||
className="shadow-sm"
|
||||
>
|
||||
{/* Logo */}
|
||||
<div
|
||||
className="h-16 flex items-center justify-center cursor-pointer border-b border-gray-100"
|
||||
onClick={() => navigate('/admin/dashboard')}
|
||||
>
|
||||
<span className="text-2xl">⚙️</span>
|
||||
{!collapsed && (
|
||||
<span className="ml-2 font-bold" style={{ color: PRIMARY_COLOR }}>
|
||||
运营管理中心
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 菜单 */}
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[selectedKey]}
|
||||
items={menuItems}
|
||||
onClick={handleMenuClick}
|
||||
style={{
|
||||
borderRight: 'none',
|
||||
}}
|
||||
/>
|
||||
</Sider>
|
||||
|
||||
<Layout>
|
||||
{/* 顶部栏 - 白色 */}
|
||||
<Header
|
||||
className="flex items-center justify-between shadow-sm"
|
||||
style={{ background: '#fff', padding: '0 24px' }}
|
||||
>
|
||||
{/* 折叠按钮 */}
|
||||
<button
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
className="text-gray-500 hover:text-gray-700 text-xl"
|
||||
>
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</button>
|
||||
|
||||
{/* 右侧工具栏 */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* 通知 */}
|
||||
<Badge count={0} size="small">
|
||||
<BellOutlined className="text-gray-500 hover:text-gray-700 text-lg cursor-pointer" />
|
||||
</Badge>
|
||||
|
||||
{/* 用户 */}
|
||||
<Dropdown
|
||||
menu={{ items: userMenuItems, onClick: handleUserMenuClick }}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div className="flex items-center gap-2 cursor-pointer px-3 py-1 rounded hover:bg-gray-50">
|
||||
<Avatar
|
||||
size="small"
|
||||
icon={<UserOutlined />}
|
||||
style={{ background: PRIMARY_COLOR }}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 text-sm">{user?.name || '管理员'}</span>
|
||||
<span className="text-xs" style={{ color: PRIMARY_COLOR }}>{user?.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
{/* 主内容区 - 浅灰 */}
|
||||
<Content
|
||||
className="overflow-auto p-6"
|
||||
style={{ background: '#f5f5f5' }}
|
||||
>
|
||||
<ErrorBoundary moduleName="运营管理">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminLayout
|
||||
Reference in New Issue
Block a user