feat(web): migrate statistics analytics workflows
This commit is contained in:
1 parent
069e2b300b
commit
d3ff37516b
7 files changed
+205
-2
No files matched your search
@@ -57,6 +57,8 @@
|
|||||||
| `/classroom-repairs` | 教室报修与维修 | SuperAdmin、AcademicAdmin、MaintenanceAdmin、MaintenanceStaff | 工单筛选、分派/开工/完工、详情、设备档案与扫码报修二维码 | verification |
|
| `/classroom-repairs` | 教室报修与维修 | SuperAdmin、AcademicAdmin、MaintenanceAdmin、MaintenanceStaff | 工单筛选、分派/开工/完工、详情、设备档案与扫码报修二维码 | verification |
|
||||||
| `/grades/:sheetId/statistics` | 课程成绩统计 | 具备该成绩单数据范围的已登录用户 | 班级/专业/学院/全校分数分布、对比、课程讨论与互动 | verification |
|
| `/grades/:sheetId/statistics` | 课程成绩统计 | 具备该成绩单数据范围的已登录用户 | 班级/专业/学院/全校分数分布、对比、课程讨论与互动 | verification |
|
||||||
| `/event-analytics` | 运行数据分析 | SuperAdmin、AcademicAdmin、CollegeAdmin、Leader | ClickHouse 考勤、成绩与全校范围访问审计只读聚合 | verification |
|
| `/event-analytics` | 运行数据分析 | SuperAdmin、AcademicAdmin、CollegeAdmin、Leader | ClickHouse 考勤、成绩与全校范围访问审计只读聚合 | verification |
|
||||||
|
| `/statistics` | 统计报表 | SuperAdmin、AcademicAdmin、CollegeAdmin、Leader | 学生、课程、成绩、通过率、教师工作量和教室利用率;筛选、可视化和 Excel 导出 | verification |
|
||||||
|
| `/grade-analytics` | 成绩分析中心 | SuperAdmin、AcademicAdmin、CollegeAdmin、Leader、Teacher | 教学班/课程成绩比较、刷新设置、Word 报告与课程讨论 | verification |
|
||||||
| `/plugins` | 统一插件平台 | SuperAdmin | 运行状态、签名包暂存/激活/回滚、无数据删除的安全停用 | verification |
|
| `/plugins` | 统一插件平台 | SuperAdmin | 运行状态、签名包暂存/激活/回滚、无数据删除的安全停用 | verification |
|
||||||
| `/activate` | 学生账号激活 | 公开 | 学籍选项、身份匹配、强密码和一次性激活 | verification |
|
| `/activate` | 学生账号激活 | 公开 | 学籍选项、身份匹配、强密码和一次性激活 | verification |
|
||||||
| `/verify/:verificationCode?` | 电子凭证验真 | 公开 | 唯一编号、实时状态、签发信息和 PDF 哈希 | verification |
|
| `/verify/:verificationCode?` | 电子凭证验真 | 公开 | 唯一编号、实时状态、签发信息和 PDF 哈希 | verification |
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { lazy, Suspense, useEffect } from 'react'
|
import { lazy, Suspense, useEffect, type ReactNode } from 'react'
|
||||||
import { App as AntdApp, ConfigProvider, Skeleton } from 'antd'
|
import { App as AntdApp, ConfigProvider, Skeleton } from 'antd'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'
|
||||||
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import zhCN from 'antd/locale/zh_CN'
|
import zhCN from 'antd/locale/zh_CN'
|
||||||
import { useAuthStore } from '../stores/auth'
|
import { useAuthStore } from '../stores/auth'
|
||||||
|
import http from '../api/http'
|
||||||
|
|
||||||
const LoginPage = lazy(() => import('../pages/LoginPage'))
|
const LoginPage = lazy(() => import('../pages/LoginPage'))
|
||||||
const DashboardPage = lazy(() => import('../pages/DashboardPage'))
|
const DashboardPage = lazy(() => import('../pages/DashboardPage'))
|
||||||
@@ -57,6 +58,8 @@ const CourseGradeStatisticsPage = lazy(() => import('../pages/CourseGradeStatist
|
|||||||
const EventAnalyticsPage = lazy(() => import('../pages/EventAnalyticsPage'))
|
const EventAnalyticsPage = lazy(() => import('../pages/EventAnalyticsPage'))
|
||||||
const PluginsPage = lazy(() => import('../pages/PluginsPage'))
|
const PluginsPage = lazy(() => import('../pages/PluginsPage'))
|
||||||
const OperationsPage = lazy(() => import('../pages/OperationsPage'))
|
const OperationsPage = lazy(() => import('../pages/OperationsPage'))
|
||||||
|
const StatisticsPage = lazy(() => import('../pages/StatisticsPage'))
|
||||||
|
const GradeAnalyticsPage = lazy(() => import('../pages/GradeAnalyticsPage'))
|
||||||
const AccountActivationPage = lazy(() => import('../pages/AccountActivationPage'))
|
const AccountActivationPage = lazy(() => import('../pages/AccountActivationPage'))
|
||||||
const OfficialDocumentVerifyPage = lazy(() => import('../pages/OfficialDocumentVerifyPage'))
|
const OfficialDocumentVerifyPage = lazy(() => import('../pages/OfficialDocumentVerifyPage'))
|
||||||
const ClassroomRepairReportPage = lazy(() => import('../pages/ClassroomRepairReportPage'))
|
const ClassroomRepairReportPage = lazy(() => import('../pages/ClassroomRepairReportPage'))
|
||||||
@@ -92,6 +95,13 @@ function SessionExpiryBridge() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PluginRoute({ pluginId, children }: { pluginId: string; children: ReactNode }) {
|
||||||
|
const runtime = useQuery({ queryKey: ['plugin-runtime'], queryFn: async () => (await http.get<{ id: string; isEnabled: boolean }[]>('/plugins/runtime')).data })
|
||||||
|
if (runtime.isLoading) return <div style={{ maxWidth: 1180, margin: '64px auto', padding: 24 }}><Skeleton active paragraph={{ rows: 5 }} /></div>
|
||||||
|
if (runtime.data?.some((item) => item.id === pluginId && !item.isEnabled)) return <Navigate to="/dashboard" replace />
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
|
|
||||||
function AppRoutes() {
|
function AppRoutes() {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div style={{ maxWidth: 1180, margin: '64px auto', padding: 24 }}><Skeleton active paragraph={{ rows: 8 }} /></div>}>
|
<Suspense fallback={<div style={{ maxWidth: 1180, margin: '64px auto', padding: 24 }}><Skeleton active paragraph={{ rows: 8 }} /></div>}>
|
||||||
@@ -162,6 +172,8 @@ function AppRoutes() {
|
|||||||
<Route path="/event-analytics" element={<EventAnalyticsPage />} />
|
<Route path="/event-analytics" element={<EventAnalyticsPage />} />
|
||||||
<Route path="/plugins" element={<PluginsPage />} />
|
<Route path="/plugins" element={<PluginsPage />} />
|
||||||
<Route path="/operations" element={<OperationsPage />} />
|
<Route path="/operations" element={<OperationsPage />} />
|
||||||
|
<Route path="/statistics" element={<PluginRoute pluginId="mingxu.statistics"><StatisticsPage /></PluginRoute>} />
|
||||||
|
<Route path="/grade-analytics" element={<PluginRoute pluginId="mingxu.statistics"><GradeAnalyticsPage /></PluginRoute>} />
|
||||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
CarryOutOutlined,
|
CarryOutOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
ToolOutlined,
|
ToolOutlined,
|
||||||
|
BarChartOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { Avatar, Badge, Button, Layout, Menu, Tag } from 'antd'
|
import { Avatar, Badge, Button, Layout, Menu, Tag } from 'antd'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
@@ -63,6 +64,8 @@ export default function AppShell() {
|
|||||||
queryFn: async () => (await http.get<{ count: number }>('/notifications/unread-count')).data.count,
|
queryFn: async () => (await http.get<{ count: number }>('/notifications/unread-count')).data.count,
|
||||||
refetchInterval: 60_000,
|
refetchInterval: 60_000,
|
||||||
})
|
})
|
||||||
|
const pluginRuntime = useQuery({ queryKey: ['plugin-runtime'], queryFn: async () => (await http.get<{ id: string; isEnabled: boolean }[]>('/plugins/runtime')).data })
|
||||||
|
const statisticsEnabled = pluginRuntime.data?.find((item) => item.id === 'mingxu.statistics')?.isEnabled !== false
|
||||||
|
|
||||||
useEffect(() => { void refreshUser().catch(() => undefined) }, [refreshUser])
|
useEffect(() => { void refreshUser().catch(() => undefined) }, [refreshUser])
|
||||||
|
|
||||||
@@ -122,6 +125,12 @@ export default function AppShell() {
|
|||||||
{ key: '/course-enrollments', icon: <BookOutlined />, label: '修读课程管理' },
|
{ key: '/course-enrollments', icon: <BookOutlined />, label: '修读课程管理' },
|
||||||
{ key: '/campus-wallets', icon: <WalletOutlined />, label: '学生余额管理' },
|
{ key: '/campus-wallets', icon: <WalletOutlined />, label: '学生余额管理' },
|
||||||
] : []),
|
] : []),
|
||||||
|
...(statisticsEnabled && roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Leader'].includes(role)) ? [
|
||||||
|
{ key: '/statistics', icon: <BarChartOutlined />, label: '统计报表' },
|
||||||
|
] : []),
|
||||||
|
...(statisticsEnabled && roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Leader', 'Teacher'].includes(role)) ? [
|
||||||
|
{ key: '/grade-analytics', icon: <LineChartOutlined />, label: roles.includes('Teacher') && !roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Leader'].includes(role)) ? '教学班成绩分析' : '成绩分析中心' },
|
||||||
|
] : []),
|
||||||
...(roles.some((role) => ['SuperAdmin', 'AcademicAdmin'].includes(role)) ? [
|
...(roles.some((role) => ['SuperAdmin', 'AcademicAdmin'].includes(role)) ? [
|
||||||
{ key: '/venue-displays', icon: <FundProjectionScreenOutlined />, label: '场地信息展牌' },
|
{ key: '/venue-displays', icon: <FundProjectionScreenOutlined />, label: '场地信息展牌' },
|
||||||
{ key: '/base-data/organization', icon: <ApartmentOutlined />, label: '组织机构' },
|
{ key: '/base-data/organization', icon: <ApartmentOutlined />, label: '组织机构' },
|
||||||
|
|||||||
Loaded 3 of 7 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user