chore(deploy): finalize 0309 SAE rollout updates
Sync deployment documentation to the final successful SAE state and clear pending deployment checklist items. Include backend/frontend/R hardening and diagnostics improvements required for stable production behavior. Made-with: Cursor
This commit is contained in:
@@ -34,7 +34,7 @@ const PRIMARY_COLOR = '#10b981'
|
||||
* - 权限检查:SUPER_ADMIN / PROMPT_ENGINEER
|
||||
*/
|
||||
const AdminLayout = () => {
|
||||
const { isAuthenticated, isLoading, user, logout } = useAuth()
|
||||
const { isAuthenticated, isLoading, user, logout, hasPermission } = useAuth()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
@@ -55,8 +55,9 @@ const AdminLayout = () => {
|
||||
|
||||
// 权限检查:可进入管理端的角色
|
||||
const adminAllowedRoles = ['SUPER_ADMIN', 'PROMPT_ENGINEER', 'IIT_OPERATOR', 'PHARMA_ADMIN', 'HOSPITAL_ADMIN']
|
||||
const hasUserOps = hasPermission('ops:user-ops')
|
||||
const userRole = user?.role || ''
|
||||
if (!adminAllowedRoles.includes(userRole)) {
|
||||
if (!adminAllowedRoles.includes(userRole) && !hasUserOps) {
|
||||
return (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
@@ -133,6 +134,10 @@ const AdminLayout = () => {
|
||||
} else if (userRole === 'PHARMA_ADMIN' || userRole === 'HOSPITAL_ADMIN') {
|
||||
items.push(projectGroup)
|
||||
}
|
||||
if (hasUserOps && userRole !== 'SUPER_ADMIN') {
|
||||
if (items.length > 0) items.push({ type: 'divider' })
|
||||
items.push(bizGroup)
|
||||
}
|
||||
return items
|
||||
})()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import type { MenuProps } from 'antd'
|
||||
import { MODULES } from '../modules/moduleRegistry'
|
||||
import { useAuth } from '../auth'
|
||||
import apiClient from '../../common/api/axios'
|
||||
|
||||
/**
|
||||
* 顶部导航栏组件
|
||||
@@ -25,7 +26,7 @@ import { useAuth } from '../auth'
|
||||
const TopNavigation = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { user, logout: authLogout, hasModule } = useAuth()
|
||||
const { user, logout: authLogout, hasModule, hasPermission } = useAuth()
|
||||
|
||||
// 根据用户模块权限过滤可显示的模块
|
||||
const availableModules = MODULES.filter(module => {
|
||||
@@ -40,7 +41,20 @@ const TopNavigation = () => {
|
||||
|
||||
// 检查用户权限,决定显示哪些切换入口
|
||||
const userRole = user?.role || ''
|
||||
const canAccessAdmin = ['SUPER_ADMIN', 'PROMPT_ENGINEER', 'IIT_OPERATOR'].includes(userRole)
|
||||
const canAccessAdmin = ['SUPER_ADMIN', 'PROMPT_ENGINEER', 'IIT_OPERATOR'].includes(userRole) || hasPermission('ops:user-ops')
|
||||
const reportTopNavClick = async (moduleName: string): Promise<void> => {
|
||||
try {
|
||||
await apiClient.post('/api/v1/auth/activity', {
|
||||
module: 'SYSTEM',
|
||||
feature: '顶部导航点击',
|
||||
action: 'CLICK',
|
||||
info: moduleName,
|
||||
})
|
||||
} catch {
|
||||
// 埋点失败不影响导航
|
||||
}
|
||||
}
|
||||
|
||||
const canAccessOrg = ['HOSPITAL_ADMIN', 'PHARMA_ADMIN', 'DEPARTMENT_ADMIN', 'SUPER_ADMIN'].includes(userRole)
|
||||
|
||||
// 用户菜单 - 动态构建
|
||||
@@ -121,6 +135,7 @@ const TopNavigation = () => {
|
||||
<div
|
||||
key={module.id}
|
||||
onClick={() => {
|
||||
void reportTopNavClick(module.name)
|
||||
if (module.isExternal && module.externalUrl) {
|
||||
window.open(module.externalUrl, '_blank', 'noopener');
|
||||
} else {
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
* 运营统计 API
|
||||
*/
|
||||
|
||||
import { authRequest } from '@/framework/request';
|
||||
import apiClient from '@/common/api/axios';
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
export interface OverviewData {
|
||||
dau: number;
|
||||
mau: number;
|
||||
dat: number;
|
||||
exportCount: number;
|
||||
apiTokenTotal: number;
|
||||
topActiveUser: {
|
||||
userId: string;
|
||||
userName: string | null;
|
||||
actionCount: number;
|
||||
} | null;
|
||||
moduleStats: Record<string, number>;
|
||||
}
|
||||
|
||||
@@ -54,29 +61,29 @@ export interface UserOverview {
|
||||
* 获取今日大盘数据
|
||||
*/
|
||||
export async function getOverview(): Promise<OverviewData> {
|
||||
const res = await authRequest.get<{ success: boolean; data: OverviewData }>(
|
||||
const res = await apiClient.get<{ success: boolean; data: OverviewData }>(
|
||||
'/api/admin/stats/overview'
|
||||
);
|
||||
return res.data;
|
||||
return res.data.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实时流水账
|
||||
*/
|
||||
export async function getLiveFeed(limit = 100): Promise<ActivityLog[]> {
|
||||
const res = await authRequest.get<{ success: boolean; data: ActivityLog[] }>(
|
||||
const res = await apiClient.get<{ success: boolean; data: ActivityLog[] }>(
|
||||
`/api/admin/stats/live-feed?limit=${limit}`
|
||||
);
|
||||
return res.data;
|
||||
return res.data.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户360画像
|
||||
*/
|
||||
export async function getUserOverview(userId: string): Promise<UserOverview> {
|
||||
const res = await authRequest.get<{ success: boolean; data: UserOverview }>(
|
||||
const res = await apiClient.get<{ success: boolean; data: UserOverview }>(
|
||||
`/api/admin/users/${userId}/overview`
|
||||
);
|
||||
return res.data;
|
||||
return res.data.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
UserOutlined,
|
||||
BankOutlined,
|
||||
ExportOutlined,
|
||||
FireOutlined,
|
||||
ApiOutlined,
|
||||
MessageOutlined,
|
||||
BookOutlined,
|
||||
SearchOutlined,
|
||||
@@ -23,7 +25,7 @@ import {
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { getOverview, getLiveFeed } from '../api/statsApi';
|
||||
import type { OverviewData, ActivityLog } from '../api/statsApi';
|
||||
import type { ActivityLog } from '../api/statsApi';
|
||||
|
||||
// ==================== 模块图标映射 ====================
|
||||
|
||||
@@ -174,7 +176,7 @@ export default function StatsDashboardPage() {
|
||||
|
||||
{/* 核心指标卡片 */}
|
||||
<Row gutter={[16, 16]} className="mb-6">
|
||||
<Col xs={24} sm={8}>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<Card className="shadow-sm hover:shadow-md transition-shadow">
|
||||
<Statistic
|
||||
title={<span className="text-gray-600">今日活跃医生 (DAU)</span>}
|
||||
@@ -185,7 +187,18 @@ export default function StatsDashboardPage() {
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<Card className="shadow-sm hover:shadow-md transition-shadow">
|
||||
<Statistic
|
||||
title={<span className="text-gray-600">近30天活跃用户 (MAU)</span>}
|
||||
value={overview?.mau ?? 0}
|
||||
prefix={<UserOutlined className="text-indigo-500" />}
|
||||
loading={overviewLoading}
|
||||
valueStyle={{ color: '#4f46e5', fontWeight: 'bold' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<Card className="shadow-sm hover:shadow-md transition-shadow">
|
||||
<Statistic
|
||||
title={<span className="text-gray-600">今日活跃租户 (DAT)</span>}
|
||||
@@ -196,7 +209,7 @@ export default function StatsDashboardPage() {
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<Card className="shadow-sm hover:shadow-md transition-shadow">
|
||||
<Statistic
|
||||
title={<span className="text-gray-600">今日导出次数</span>}
|
||||
@@ -209,6 +222,31 @@ export default function StatsDashboardPage() {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} className="mb-6">
|
||||
<Col xs={24} md={12}>
|
||||
<Card className="shadow-sm hover:shadow-md transition-shadow">
|
||||
<Statistic
|
||||
title={<span className="text-gray-600">今日最活跃用户</span>}
|
||||
value={overview?.topActiveUser?.userName || '暂无'}
|
||||
prefix={<FireOutlined className="text-orange-500" />}
|
||||
suffix={overview?.topActiveUser ? `${overview.topActiveUser.actionCount} 次` : ''}
|
||||
loading={overviewLoading}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Card className="shadow-sm hover:shadow-md transition-shadow">
|
||||
<Statistic
|
||||
title={<span className="text-gray-600">今日 API Token 用量</span>}
|
||||
value={overview?.apiTokenTotal ?? 0}
|
||||
prefix={<ApiOutlined className="text-cyan-500" />}
|
||||
loading={overviewLoading}
|
||||
valueStyle={{ color: '#0891b2', fontWeight: 'bold' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 模块使用统计 */}
|
||||
{overview?.moduleStats && Object.keys(overview.moduleStats).length > 0 && (
|
||||
<Card
|
||||
|
||||
Reference in New Issue
Block a user