Files
RiskAgent/web/src/routing/DefaultView.tsx
T
freedakgmail c670b9e454 外包风险评估系统:领域引擎+前端+服务端持久化与生产部署
- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解
- 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护)
- 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理
- 专业图标体系替换全部emoji、看板美化
- 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC
- 444单测+204前端测试+51 e2e
2026-06-13 01:06:39 +08:00

146 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* DefaultView — 角色化默认首屏视图(task 23.3Req 25.125.5)。
*
* 给定登录用户角色,本组件渲染与该角色匹配的默认视图首屏,并将角色匹配的功能入口
* 呈现于无需额外导航即可见的位置(Req 25.4)。目标视图复用任务 15 的角色化视图
* renderView/renderPortfolio)作为导航目标;此处呈现进入这些视图的功能入口
* (而非领域侧的完整视图实现)。
*
* 路由目标由纯函数 `resolveDefaultView` 决定(见 routing.tsProperty 81):
* 商务/销售 → SalesView、风控 → RiskView、管理层 → ManagementDashboard。
* 对 `无角色` 渲染「需分配角色」提示视图,且不呈现任何评估数据(Req 25.5)。
*
* 可访问性:以 `<section>` 区域 + 标题层级表达结构;功能入口以 `Nav` 语义化导航
* 暴露(landmark),便于辅助技术直接定位(Req 25.4)。视觉值统一取自 Design Tokens。
*/
import type { CSSProperties } from 'react';
import { Card, Nav } from '../design-system/index.js';
import type { NavItem } from '../design-system/index.js';
import { colorVar, FONT_FAMILY, space, typographyStyle } from '../design-system/components/styles.js';
import { resolveDefaultView } from './routing.js';
import type { Role, Route } from './routing.js';
/** 单条功能入口定义(角色匹配,呈现为导航目标)。 */
interface EntryPoint {
/** 唯一键(亦作为导航目标标识)。 */
readonly key: string;
/** 显示文本。 */
readonly label: string;
/** 进入目标视图的路由片段(占位链接,领域侧视图于任务 15 实现)。 */
readonly href: string;
}
/** 每个角色默认视图的标题与可见功能入口(Req 25.4)。 */
interface ViewDescriptor {
/** 默认视图标题。 */
readonly heading: string;
/** 功能入口导航的无障碍标签。 */
readonly navLabel: string;
/** 角色匹配的功能入口集合(首屏可见,无需额外导航)。 */
readonly entryPoints: readonly EntryPoint[];
}
/**
* 路由 → 默认视图描述符。功能入口对应任务 15 角色化视图的关键能力区块:
* - SalesView:结论 / 接受条件 / 风险调整后报价(Req 13.1)。
* - RiskView:评分明细 / 红线检查 / 缺口尽调(Req 13.2)。
* - ManagementDashboard:风险热力图 / TopN 风险 / 利润 vs 风险看板(Req 13.3)。
* `AssignRolePrompt` 不在此表内——其不呈现任何评估数据(Req 25.5)。
*/
const VIEW_DESCRIPTORS: Readonly<Record<Exclude<Route, 'AssignRolePrompt'>, ViewDescriptor>> = {
SalesView: {
heading: '商务/销售视图',
navLabel: '商务/销售功能入口',
entryPoints: [
{ key: 'sales-conclusion', label: '评估结论', href: '#/sales/conclusion' },
{ key: 'sales-acceptance', label: '接受条件', href: '#/sales/acceptance' },
{ key: 'sales-quote', label: '风险调整后报价', href: '#/sales/quote' },
],
},
RiskView: {
heading: '风控视图',
navLabel: '风控功能入口',
entryPoints: [
{ key: 'risk-scoring', label: '评分明细', href: '#/risk/scoring' },
{ key: 'risk-redline', label: '红线检查', href: '#/risk/redline' },
{ key: 'risk-gap', label: '缺口尽调', href: '#/risk/gap' },
],
},
ManagementDashboard: {
heading: '管理层看板',
navLabel: '管理层功能入口',
entryPoints: [
{ key: 'mgmt-heatmap', label: '风险热力图', href: '#/management/heatmap' },
{ key: 'mgmt-topn', label: 'TopN 风险', href: '#/management/topn' },
{ key: 'mgmt-profit-risk', label: '利润 vs 风险看板', href: '#/management/profit-risk' },
],
},
} as const;
/** `DefaultView` 组件属性。 */
export interface DefaultViewProps {
/** 登录用户角色。 */
readonly role: Role;
}
/**
* 渲染角色匹配的默认首屏视图。
*
* - 已分配业务角色:渲染对应视图标题与首屏可见的功能入口导航(Req 25.1–25.4)。
* - 无角色:渲染「需分配角色」提示,且不呈现任何评估数据(Req 25.5)。
*/
export function DefaultView({ role }: DefaultViewProps): JSX.Element {
const { route, showsAssessmentData } = resolveDefaultView(role);
const containerStyle: CSSProperties = {
display: 'flex',
flexDirection: 'column',
gap: `${space(4)}px`,
fontFamily: FONT_FAMILY,
color: colorVar('color.text.primary'),
};
if (route === 'AssignRolePrompt') {
// Req 25.5:未分配业务角色 → 提示分配角色,且不呈现任何评估数据。
return (
<section
data-default-view="true"
data-route={route}
data-shows-assessment-data={showsAssessmentData}
aria-label="需分配角色"
style={containerStyle}
>
<h1 style={{ ...typographyStyle('heading'), margin: 0 }}></h1>
<Card title="暂无可访问的评估视图">
<p style={{ ...typographyStyle('body'), margin: 0 }}>
/
</p>
</Card>
</section>
);
}
const descriptor = VIEW_DESCRIPTORS[route];
const navItems: readonly NavItem[] = descriptor.entryPoints.map((entry) => ({
key: entry.key,
label: entry.label,
href: entry.href,
}));
return (
<section
data-default-view="true"
data-route={route}
data-shows-assessment-data={showsAssessmentData}
aria-label={descriptor.heading}
style={containerStyle}
>
<h1 style={{ ...typographyStyle('heading'), margin: 0 }}>{descriptor.heading}</h1>
{/* Req 25.4:角色匹配的功能入口呈现于无需额外导航即可见的位置。 */}
<Nav items={navItems} ariaLabel={descriptor.navLabel} />
</section>
);
}