feat: add system operations dashboard
Release / release (push) Canceled after 0s

This commit is contained in:
Qiufeng
2026-08-19 13:51:48 +08:00
parent 8e615b895e
commit 12d78b4c86
45 changed files with 2921 additions and 48 deletions
+3 -3
View File
@@ -171,12 +171,12 @@ describe('frontend machine contracts', () => {
expect(paymentApi).toContain("responseType: 'blob'");
});
it('declares every PAGE-01 through PAGE-23 exactly once', () => {
const expected = Array.from({ length: 23 }, (_, index) => `PAGE-${String(index + 1).padStart(2, '0')}`);
it('declares every PAGE-01 through PAGE-24 exactly once', () => {
const expected = Array.from({ length: 24 }, (_, index) => `PAGE-${String(index + 1).padStart(2, '0')}`);
const routePageIds = routesContract.routes.map((route) => route.pageId);
const componentPageIds = componentsContract.pages.map((page) => page.pageId);
expect([...routePageIds].sort()).toEqual([...expected].sort());
expect(new Set(routePageIds).size).toBe(23);
expect(new Set(routePageIds).size).toBe(24);
expect([...componentPageIds].sort()).toEqual([...expected].sort());
});
+49
View File
@@ -0,0 +1,49 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
import { normalizeDashboard } from '../src/api/dashboard';
describe('pAGE-24 dashboard contracts', () => {
const pageSource = readFileSync(resolve(process.cwd(), 'src/pages/overview/DashboardPage.vue'), 'utf8');
const chartSource = readFileSync(resolve(process.cwd(), 'src/pages/overview/components/DashboardChart.vue'), 'utf8');
it('normalizes incomplete server data without creating fake business values', () => {
const view = normalizeDashboard({
system: { services: null } as any,
metrics: null,
trend: { points: null } as any,
lifecycle: undefined,
risks: null,
geography: { available: false, regions: null } as any,
activities: undefined,
} as unknown as Partial<ReturnType<typeof normalizeDashboard>>);
expect(view.metrics).toEqual([]);
expect(view.trend.points).toEqual([]);
expect(view.lifecycle).toEqual([]);
expect(view.risks).toEqual([]);
expect(view.activities).toEqual([]);
expect(view.geography.available).toBe(false);
expect(view.geography.regions).toEqual([]);
});
it('defines the hybrid cockpit and explicit permission-safe geography fallback', () => {
expect(pageSource).toContain('data-scene');
expect(pageSource).toContain('进入大屏');
expect(pageSource).toContain('退出大屏');
expect(pageSource).toContain('dashboard.geography.available');
expect(pageSource).toContain('补齐项目省、市编码后,将自动启用地图');
expect(pageSource).toContain('当前身份没有查看仪表盘或相关业务数据的权限');
expect(pageSource).toContain('setInterval(() => void loadDashboard(true), 60000)');
expect(pageSource).not.toMatch(/<button\b|<input\b|<select\b/);
});
it('uses modular ECharts with resize and disposal lifecycle', () => {
expect(chartSource).toContain("from 'echarts/core'");
expect(chartSource).toContain('ResizeObserver');
expect(chartSource).toContain('chart.dispose()');
expect(chartSource).toContain('aria-label="label"');
});
});
+20 -1
View File
@@ -11,7 +11,7 @@ import { canAccess, filterMenu, hasRoleAccess } from '../src/store/modules/menu-
describe('router and TDesign menu contracts', () => {
it('keeps every PAGE route declared and the static deep-link fallback in the router', () => {
expect(routesContract.routes.map((route) => route.pageId).sort()).toEqual(
Array.from({ length: 23 }, (_, index) => `PAGE-${String(index + 1).padStart(2, '0')}`),
Array.from({ length: 24 }, (_, index) => `PAGE-${String(index + 1).padStart(2, '0')}`),
);
expect(routesContract.routes.find((route) => route.pageId === 'PAGE-15')?.path).toBe('/finance/payments');
expect(readFileSync(resolve(process.cwd(), 'src/router/index.ts'), 'utf8')).toContain('createWebHistory');
@@ -32,12 +32,31 @@ describe('router and TDesign menu contracts', () => {
}
};
collectPaths(allRoutes);
expect(flattenedPaths).toContain('/dashboard');
expect(flattenedPaths).toContain('/projects/');
expect(flattenedPaths).not.toContain('/dashboard/base');
expect(flattenedPaths).not.toContain('/detail/base');
expect(flattenedPaths).not.toContain('/list/base');
});
it('places the dashboard first and hides it without its dedicated permission', () => {
const dashboard = allRoutes.find((route) => route.path === '/dashboard');
expect(dashboard?.meta?.orderNo).toBe(1);
expect(dashboard?.children?.[0]?.meta?.permission).toBe('dashboard:overview:view');
const withoutDashboard = filterMenu(allRoutes, {
currentRole: 'FINANCE_MANAGER',
permissions: ['workflow:task:view'],
});
expect(withoutDashboard.some((route) => route.path === '/dashboard')).toBe(false);
const withDashboard = filterMenu(allRoutes, {
currentRole: 'CUSTOM_ROLE',
permissions: ['dashboard:overview:view'],
});
expect(withDashboard[0]?.path).toBe('/dashboard');
});
it('filters menu leaves with the same role and permission rules used by route access', () => {
const projectIdentity = { currentRole: 'PROJECT_MANAGER', permissions: ['project:project:view'] };
const financeIdentity = {
+2 -2
View File
@@ -4,13 +4,13 @@ import type { RoleView } from '../src/api/auth';
import { resolveWorkbenchRoute } from '../src/store/modules/workbench-route';
const roles: RoleView[] = [
{ code: 'SYSTEM_ADMIN', name: '系统管理员', workbenchRoute: '/governance/settings' },
{ code: 'SYSTEM_ADMIN', name: '系统管理员', workbenchRoute: '/dashboard' },
{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' },
];
describe('active identity workbench route', () => {
it('uses the route supplied for the active role', () => {
expect(resolveWorkbenchRoute('SYSTEM_ADMIN', roles)).toBe('/governance/settings');
expect(resolveWorkbenchRoute('SYSTEM_ADMIN', roles)).toBe('/dashboard');
expect(resolveWorkbenchRoute('FINANCE_MANAGER', roles)).toBe('/workbench/finance');
});