外包风险评估系统:领域引擎+前端+服务端持久化与生产部署

- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解
- 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护)
- 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理
- 专业图标体系替换全部emoji、看板美化
- 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC
- 444单测+204前端测试+51 e2e
This commit is contained in:
freedakgmail
2026-06-13 01:06:39 +08:00
commit c670b9e454
404 changed files with 61820 additions and 0 deletions
+145
View File
@@ -0,0 +1,145 @@
/**
* 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>
);
}