commit 7d1cc40d95d7c8bfd067258ee471f2e98cc74d85 Author: william.wan Date: Wed Feb 11 14:07:01 2026 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d600b6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d893b3 --- /dev/null +++ b/README.md @@ -0,0 +1,201 @@ +# RMO一站式临床试验风险管理网站 + +## 项目简介 + +RMO(Risk Management Organization)一站式临床试验风险管理网站,旨在介绍RMO模式,清晰展示申办者、研究机构、SMO、CRO在临床试验中各自应承担的职责,说明保险和保证金方式如何保障受试者安全。 + +## 技术栈 + +- **前端框架**: React 18.2.0 +- **开发语言**: TypeScript +- **路由**: React Router DOM 6.20.0 +- **构建工具**: Vite 5.0.8 +- **样式**: CSS3 (原生CSS,无UI框架依赖) + +## 项目结构 + +``` +rmo-website/ +├── public/ # 静态资源 +├── src/ +│ ├── components/ # 公共组件 +│ │ ├── Header.tsx # 导航头部 +│ │ ├── Footer.tsx # 页脚 +│ │ └── Layout.tsx # 布局组件 +│ ├── pages/ # 页面组件 +│ │ ├── Home.tsx # 首页 +│ │ ├── Company.tsx # 公司介绍 +│ │ ├── Services.tsx # 我们的服务 +│ │ ├── Sponsor.tsx # 申办方专区 +│ │ ├── Institution.tsx # 试验机构专区 +│ │ ├── Participant.tsx # 受试者专区 +│ │ └── ServiceProvider.tsx # 服务方专区 +│ ├── App.tsx # 主应用组件 +│ ├── main.tsx # 入口文件 +│ └── index.css # 全局样式 +├── index.html # HTML模板 +├── package.json # 项目配置 +├── tsconfig.json # TypeScript配置 +├── vite.config.ts # Vite配置 +└── README.md # 项目说明 +``` + +## 功能特性 + +### 主要页面 + +1. **首页** + - 一站式风险管理模式图展示 + - RMO模式/理念/价值介绍 + - RMO模式服务方介绍 + - 快速导航到各角色专区 + +2. **公司介绍** + - 临研安公司简介 + - 活动动态(研讨会、发布会、展会、培训会) + - 资源中心(法律法规、团体标准、行业共识、视频) + +3. **我们的服务** + - 自保(专项风险管理基金) + - 风险减量服务 + - 外溢风险管理服务 + - 保险服务 + +4. **申办方专区** + - 风险管理体系(风险识别、风险评估、风险管理策略) + - RMO模式解决方案 + - 操作流程 + +5. **试验机构专区** + - 试验机构关注要点 + - 研究者支持 + - 伦理委员会支持 + +6. **受试者专区** + - 临床试验介绍 + - 受试者权益 + - 损害救济 + +7. **服务方专区** + - CRO支持 + - CDMO支持 + - SMO支持 + +## 安装和运行 + +### 前置要求 + +- Node.js >= 16.0.0 +- npm >= 7.0.0 或 yarn >= 1.22.0 + +### 安装依赖 + +```bash +npm install +``` + +或 + +```bash +yarn install +``` + +### 开发模式运行 + +```bash +npm run dev +``` + +或 + +```bash +yarn dev +``` + +项目将在 `http://localhost:3000` 启动,浏览器会自动打开。 + +### 构建生产版本 + +```bash +npm run build +``` + +或 + +```bash +yarn build +``` + +构建产物将输出到 `dist` 目录。 + +### 预览生产版本 + +```bash +npm run preview +``` + +或 + +```bash +yarn preview +``` + +## 开发说明 + +### 添加新页面 + +1. 在 `src/pages/` 目录下创建新的页面组件 +2. 在 `src/App.tsx` 中添加路由配置 +3. 在 `src/components/Header.tsx` 中添加导航链接 + +### 样式规范 + +- 使用CSS变量定义主题色和通用样式(见 `src/index.css`) +- 每个组件/页面有独立的CSS文件 +- 遵循响应式设计,支持移动端访问 + +### 代码规范 + +- 使用TypeScript进行类型检查 +- 遵循React Hooks最佳实践 +- 组件采用函数式组件 + +## 浏览器支持 + +- Chrome (最新版本) +- Firefox (最新版本) +- Safari (最新版本) +- Edge (最新版本) + +## 项目特点 + +- ✅ 响应式设计,支持PC、平板、手机访问 +- ✅ 现代化UI设计,专业美观 +- ✅ 清晰的导航结构 +- ✅ 完整的内容展示 +- ✅ 快速加载,性能优化 +- ✅ TypeScript类型安全 + +## 待完善功能 + +- [ ] 添加实际的一站式风险管理模式图(图片/可视化图表) +- [ ] 实现资源中心的文件下载功能 +- [ ] 添加在线咨询功能 +- [ ] 实现服务申请表单提交 +- [ ] 添加数据统计展示 +- [ ] 实现用户登录系统(如需要) + +## 许可证 + +本项目为内部项目,版权归临研安所有。 + +## 联系方式 + +如有问题或建议,请联系: +- 邮箱:info@rmo.com +- 电话:400-XXX-XXXX + +--- + +**最后更新**: 2025年1月 + diff --git a/RMO网站需求文档.docx b/RMO网站需求文档.docx new file mode 100644 index 0000000..312e5ff Binary files /dev/null and b/RMO网站需求文档.docx differ diff --git a/RMO网站需求文档.md b/RMO网站需求文档.md new file mode 100644 index 0000000..102758d --- /dev/null +++ b/RMO网站需求文档.md @@ -0,0 +1,1095 @@ +# RMO一站式临床试验风险管理网站需求文档 + +## 一、项目概述 + +### 1.1 项目背景 +- **RMO定义**:RMO(Risk Management Organization,风险管理组织)是一个"一站式的、涉及到患者/受试者安全责任的保险与保障方案"。 +- **项目目的**:我们需要做一个网站,用以介绍RMO的模式,介绍申办者、研究机构、SMO、CRO在临床试验中各自应承担的职责。从首页延伸出各角色的职责、以及保险的方法如何进行保证;保证(金)方式如何进行保障。最终确保受试者在临床试验过程中的安全。 +- **该网站所有内容围绕受试者安全保障与患者安全保障**。 +- **法律依据**:在临床试验中,申办者需要承担受试者保护的责任依据是《临床试验质量管理规范》第三十九条, + (一)申办者应当向研究者和临床试验机构提供与临床试验相关的法律上、经济上的保险或者保证,并与临床试验的风险性质和风险程度相适应。但不包括研究者和临床试验机构自身的过失所致的损害。 +- **持有人责任**:针对药品上市后的持有人责任,根据《药品管理法》要求,持有人需要有风险管理能力,承担药品安全责任。 + + +### 1.2 网站目标 +- 协助药企(申办者、持有人)规划患者、受试者安全责任保障的方式、方法; +- 清晰展示申办者、持有人、研究机构、SMO、CRO在临床试验中各自应承担的职责 +- 说明保险和保证方式如何保障受试者安全,帮助申办者承担其职责 +- 为不同角色(申办方、持有人、试验机构、受试者、服务方)提供针对性的信息和服务 +- **登录后系统**:为已登录用户提供工作台、项目管理、保障评估、智能工具等功能,根据用户角色呈现有权限的内容 + +### 1.3 目标用户 +- **主要用户**:申办者(制药企业、生物技术公司等)、持有人 +- **次要用户**:研究机构、研究者、伦理委员会 +- **其他用户**:CRO、CDMO、SMO、受试者 +- **登录后系统用户角色**: + - **投保人**:可以是申办者、持有人;如果为研究者发起的试验,需要将其同时标记为申办者。可申请保障、查看自己的项目和保障、使用智能工具等 + - **保险人**:保险公司,可查看分配给自己的项目、进行保障评估、处理报价和理赔、使用智能工具 + +--- + +## 二、网站架构与页面结构 + +### 2.1 整体架构 +网站分为**免登录浏览区**和**登录后系统区**两部分: + +#### 2.1.1 免登录浏览区(已实现) +``` +首页(免登录) +├── 主页 +│ ├── Banner(紧凑型):生命科学风险管理的保险与保证方案;含「获取报价」入口 +│ ├── 风险管理体系 +│ │ ├── 法律法规 +│ │ ├── 实践指南 +│ │ ├── 行业动态 +│ │ └── 药物警戒 +│ └── 角色专区(各方职责快捷入口) +│ +├── 风险职责(原"各方关注") +│ ├── 风险职责总览 +│ ├── 申办者职责 +│ ├── 持有人职责 +│ ├── 受试者专区 +│ ├── 研究中心 +│ └── CXO职责 +│ +├── 临床试验(原RMO模式) +│ ├── 临床试验模块首页 +│ ├── 保险方案(含「获取报价」入口) +│ ├── 保证方案 +│ └── 保险保证 +│ +├── 上市应用 +│ +├── 海外风险 +│ +├── 资源中心(原"体系管理") +│ ├── 资源中心首页 +│ ├── 实践指南 +│ ├── 培训材料 +│ └── 常见问题 +│ +└── 登录(右上角) +``` +### 2.1.2 登录后系统区(待开发) + +# +登录后依然可见登录前的菜单。如果用户没有账号,引导其注册账号。所有账号将会有管理员进行审批,以使其获得查看数据的权限。 +``` +登录后系统(需权限验证) +├── 工作台 +│ ├── 我的保障 +│ ├── 我的项目 +│ ├── 待办任务 +│ └── 快捷方式(获取报价、申请保障、申请理赔) +│ +├── 项目列表 +│ ├── 项目列表(试验项目编号、试验题目、保障范围、承保公司、承保状态) +│ └── 项目明细(详细信息、相关文档、操作按钮) +│ +├── 询价列表(需权限:保险人) +│ └── 询价列表(申办者、项目编号、试验题目、保障范围、特别约定、成交情况) +│ +├── 理赔进度(需权限:投保人、保险人) +│ └── 理赔评估列表(项目编号、试验题目、保障范围、承保公司、承保状态) +│ +└── 智能工具(所有登录用户可见) + ├── 保费测算工具(所有登录用户可见) + ├── ICF智能修改(需权限:投保人) + └── 方案风险评分(需权限:投保人) +``` +### 2.2 路由与页面结构 + +#### 2.2.0 路由架构说明 +- **免登录浏览区路由**:所有路由在 `App.tsx` 中定义,使用 `Layout` 组件包裹(Header + Footer) +- **登录后系统路由**:所有 `/dashboard/*` 路由需使用 `ProtectedRoute` 包裹,使用 `DashboardLayout` 组件(侧边栏 + 主内容区) +- **路由守卫**:未登录用户访问登录后页面应重定向到 `/login` + +### 2.3 核心页面详细需求 + +#### 2.2.1 首页(已实现) +**核心元素:** +- **Banner区域**(紧凑型) + - 标题:生命科学风险管理的保险与保证方案 + - 按钮1:保险方案(链接到 `/rmo-mode/insurance`) + - 按钮2:保证方案(链接到 `/rmo-mode/guarantee`) + - 按钮3:获取报价(打开报价申请流程:弹窗或跳转报价页面,见 2.3.2.2) +- **风险管理体系区域** + - 展示四个环节:法律法规、实践指南、行业动态、药物警戒 + - 以卡片形式展示,带图标和说明文字 +- **角色专区**(各方职责快捷入口) + - 申办者职责(链接到 `/sponsor`) + - 持有人职责(链接到 `/holder`) + - 研究中心(链接到 `/institution`) + - 受试者专区(链接到 `/participant`) + - CXO职责(链接到 `/service-provider`) + +**交互要求:** +- Banner区域紧凑型设计 +- 响应式设计,适配不同屏幕尺寸 +- 所有链接按钮可点击跳转到对应页面 + +#### 2.2.2 临床试验页面(原RMO模式,已实现) +**路由结构:** +- `/rmo-mode`:临床试验模块首页(RmoModeOverview) +- `/rmo-mode/insurance`:保险方案页面 +- `/rmo-mode/guarantee`:保证方案页面 +- `/rmo-mode/insurance-guarantee`:保险保证页面 + +**内容模块:** + +1. **保险方案页面**(已实现) + - 基础保障、全面保障说明 + - 保险团体标准核心内容 + - **获取报价入口**:页面内提供「获取报价」按钮,点击后打开报价申请流程(弹窗或报价页面,见 2.3.2.2) + - 保险服务内容: + - 保险合同审查 + - 理赔审查 + - 保险条款修订 + - 理赔规则制定 + - 团体标准制定 + - 服务供应商展示(保险公司、经纪公司 logo) + +2. **保证方案页面**(已实现) + - 保证基金的基本逻辑(图示) + - 保证基金管理形式比较(表格) + - **自保(专项风险管理基金)** + - 基金设立方式 + - 使用范围 + - 管理流程 + - 多退少补原则 + - 费用模式(比例费用、案件费用) + - **风险减量服务** + - 服务内容:风险点检查、人员培训、方案完善建议 + - 服务流程 + - 服务案例 + - **外溢风险管理服务** + - 全流程管理说明 + - 服务内容:联系、安抚、安排就医、沟通诉求、沟通合理预期 + - 服务优势 + +3. **保险保证页面**(已实现) + - 保险与保证方案的结合 + - 综合保障机制 + - 服务流程说明 + +#### 2.2.3 风险职责(原"各方关注",已实现) +**路由结构:** +- `/concern`:风险职责总览(RiskDutiesOverview) +- `/sponsor`:申办者职责 +- `/holder`:持有人职责 +- `/participant`:受试者专区 +- `/institution`:研究中心 +- `/service-provider`:CXO职责 + +**内容需求:** + +##### 2.2.3.1 风险职责总览(已实现) +- 模块首页,展示各方职责入口卡片 +- 包含:申办者职责、持有人职责、受试者专区、研究中心、CXO职责 + +##### 2.2.3.2 申办者职责(已实现) +- 风险管理体系说明 +- RMO模式解决方案 +- 操作流程说明 + +##### 2.2.3.3 持有人职责(已实现) +- 持有人责任说明 +- 上市后药物安全责任 + +##### 2.2.3.4 研究中心(已实现) +- 试验机构关注要点 +- 研究者职责 +- 伦理委员会职责 + +##### 2.2.3.5 受试者专区(已实现) +- 临床试验介绍 +- 受试者权益保障 +- 损害救济说明 + +##### 2.2.3.6 CXO职责(已实现) +- CRO支持 +- CDMO支持 +- SMO支持 + +#### 2.2.4 资源中心(原"体系管理",已实现) +**路由结构:** +- `/system-management`:资源中心首页(ResourceCenterOverview) +- `/system-management/practice-guide`:实践指南 +- `/system-management/training`:培训材料 +- `/system-management/faq` 或 `/faq`:常见问题 + +**内容模块:** + +1. **实践指南**(已实现) + - 操作指南文档 + - 最佳实践案例 + - 流程规范 + +2. **培训材料**(已实现) + - 培训视频 + - 培训文档 + - 培训课程 + +3. **常见问题**(已实现) + - FAQ列表 + - 问题分类 + - 搜索功能(待实现) + +#### 2.2.5 上市应用(已实现) +- **路由**:`/post-market` +- **内容**:上市应用相关说明 + +#### 2.2.6 海外风险(已实现) +- **路由**:`/overseas` +- **内容**:海外风险相关说明 + +#### 2.2.7 登录页面(已实现) +- **路由**:`/login` +- **功能**:账号密码登录表单 +- **后续**:登录成功后根据用户角色跳转到工作台(`/dashboard`) + +### 2.3 登录后系统详细需求(待开发) + +#### 2.3.1 系统架构 +- **布局**:侧边栏导航 + 主内容区(DashboardLayout) +- **侧边栏导航结构**: + - 工作台 + - 项目列表(需权限:投保人) + - 询价列表(需权限:保险人) + - 理赔进度(需权限:投保人、保险人) + - 智能工具(所有登录用户可见) + - 保费测算工具(所有登录用户可见) + - ICF智能修改(需权限:投保人) + - 方案风险评分(需权限:投保人) +- **权限控制**:侧边栏菜单根据用户角色动态显示有权限的菜单项 + +#### 2.3.2 工作台页面(Dashboard) +**路径**:`/dashboard` + +**权限说明**:所有登录用户可见,但内容根据角色不同 + +##### 2.3.2.1 投保人工作台 +**页面内容:** +1. **数据统计卡片** + - **询价项目**:显示询价项目数量,点击后进入询价项目列表 + - **生效保障**:显示生效保障数量,点击后进入生效保障列表 + - **全部项目**:显示全部项目数量,点击后进入全部项目列表(包括生效中及已失效的项目) + +2. **待处理任务** + - **待处理的报价项目**:列表展示待处理的报价项目(状态含:资料填写中、已生成报价、询价中、已回显精准报价等),点击可跳转到对应报价详情/报价页面 + - **待处理的理赔**:列表展示待处理的理赔申请 + - 显示:任务标题、任务类型、创建时间、优先级 + - 点击可跳转到对应详情页 + +3. **快捷方式**(页面右上角) + - **获取报价**:打开报价申请流程(弹窗或报价页面,见 2.3.2.3),与首页、保险方案页入口一致 + - **申请理赔**:跳转到理赔申请页面(需关联项目) + - **方案介绍**:申请设计并介绍方案 + - **培训支持**:申请培训支持 + +##### 2.3.2.2 保险人工作台 +**页面内容:** +1. **数据统计卡片** + - **询价项目**:显示询价项目数量,点击后进入询价项目列表 + - **生效保障**:显示生效保障数量,点击后进入生效保障列表 + - **全部项目**:显示全部项目数量,点击后进入全部项目列表(包括生效中及已失效的项目) + +2. **待处理任务** + - **待处理的报价项目**:列表展示待处理的报价项目 + - **待处理的理赔**:列表展示待处理的理赔申请 + - 显示:任务标题、任务类型、创建时间、优先级 + - 点击可跳转到对应详情页 +- 其他角色:根据权限显示相应内容 + +##### 2.3.2.3 报价申请流程(获取报价) +**入口**:店家首页 Banner「获取报价」、保险方案页「获取报价」、登录后工作台快捷方式「获取报价」。点击后打开报价申请流程(弹窗或独立报价页面,建议统一为弹窗以保持上下文)。 + +**流程对应页面/弹窗需求:** + +1. **报价需提交的资料(第一步)** + - **表单字段**:项目方案编号、项目标题、申办者、项目分期。 + - **填写方式**: + - **手动填写**:用户逐项输入上述字段。 + - **上传项目方案**:用户上传项目方案文件,系统调用 AI 识别并解析,自动填充「项目方案编号、项目标题、申办者、项目分期」;用户可核对并修改。 + - **操作**:资料填写完成后,显示「生成报价」按钮。 + +2. **生成报价(第二步)** + - 用户点击「生成报价」后,系统调用 AI 服务,基于当前资料自动生成报价。 + - 报价结果展示在同一弹窗/页面内(AI 生成报价区域)。 + +3. **获取精准报价(第三步)** + - 用户点击「获取精准报价」后,系统将报价资料整合为标准化询价内容,以 Email 发送至各保司。 + - 前端可展示「已发送至保司,等待保司回复」等状态;该次报价申请进入「询价中」状态,可在工作台「待处理的报价项目」中查看。 + +4. **精准报价回显(系统侧 + 前端)** + - **系统侧**:从 rmo@vdano.com 拉取保司回复邮件,解析并提取各保司报价;由临研安进行审核,审核通过的报价写入报价记录并关联该次申请。 + - **前端**:报价页面/报价详情支持展示「精准报价」结果(各保司、报价内容等);工作台「待处理的报价项目」中该条状态更新为「已回显精准报价」,点击可进入报价页面查看。 + +**路由建议**(若采用独立页面):`/quote` 或 `/dashboard/quote`(新建申请),`/dashboard/quote/:id`(查看某次报价申请及精准报价结果)。若采用弹窗则无需单独路由,弹窗内可提供「查看我的报价」链接至投保人报价列表或工作台待处理任务。 + +**权限**:获取报价入口对免登录用户可开放(弹窗内可引导登录后再发精准报价);登录后工作台入口仅投保人可见。 + +#### 2.3.3 项目列表页面(ProjectList) +**路径**:`/dashboard/projects` + +**权限说明**:仅投保人可见 + +**列表字段:** +- 项目编号 +- 试验题目 +- 保障范围 +- 承保公司 +- 承保状态(如:已承保、待审核、已过期等) +- 操作按钮:查看明细、申请理赔 + +**交互功能:** +- 点击"查看明细"跳转到 `/dashboard/projects/:id`(项目明细页) +- 点击"申请理赔"跳转到理赔申请页面(需关联项目ID) +- 支持搜索、筛选(待实现) + +#### 2.3.4 项目明细页面(ProjectDetail) +**路径**:`/dashboard/projects/:id` + +**权限说明**:仅投保人可见 + +**页面内容:** +1. **基本信息** + - 项目编号 + - 试验题目 + - 申办者 + - 保障范围 + - 项目状态 + +2. **保障信息** + - 承保公司 + - 承保状态 + - 保障期限(开始时间、结束时间) + - 保障金额 + - 已使用金额 + - 剩余金额 + +3. **相关文档** + - 保险合同(可下载或预览) + - 评估报告(可下载或预览) + - 其他相关文档 + +4. **操作按钮** + - 查看明细 + - 申请理赔 + +#### 2.3.5 询价列表页面(InquiryList) +**路径**:`/dashboard/inquiries` + +**权限说明**:仅保险人可见 + +**列表字段:** +- 申办者 +- 项目编号 +- 试验题目 +- 保障范围 +- 特别约定 +- 成交情况(如:已成交、待确认、已拒绝等) + +**交互功能:** +- 点击"查看明细"跳转到 `/dashboard/inquiries/:id`(询价明细页) +- 点击"处理报价"跳转到报价处理页面(需关联项目ID) +- 支持搜索、筛选(待实现) + +#### 2.3.6 询价明细页面(InquiryDetail) +**路径**:`/dashboard/inquiries/:id` + +**权限说明**:仅保险人可见 + +**页面内容:** +1. **基本信息** + - 申办者 + - 项目编号 + - 试验题目 + - 保障范围 + - 特别约定 + +2. **详细信息** + - 项目详细信息展示 + +3. **风险评估** + - 风险评估结果展示 + +4. **操作按钮** + - 查看明细 + - 处理报价 + +#### 2.3.7 理赔进度页面(ClaimProgress) +**路径**:`/dashboard/claims` + +**权限说明**:投保人、保险人可见 + +##### 2.3.7.1 投保人理赔进度 +**列表字段:** +- 项目编号 +- 试验题目 +- 保障范围 +- 承保公司 +- 承保状态 + +**交互功能:** +- 点击"查看明细"跳转到 `/dashboard/claims/:id`(理赔评估详情页) +- 点击"申请理赔"跳转到理赔申请页面(需关联项目ID) + +**理赔评估明细页内容:** +- 详细信息 +- 相关文档 +- 操作按钮:查看明细、申请理赔 + +##### 2.3.7.2 保险人理赔进度 +**列表字段:** +- 项目编号 +- 试验题目 +- 保障范围 +- 承保公司 +- 承保状态 + +**交互功能:** +- 点击"查看明细"跳转到 `/dashboard/claims/:id`(理赔评估详情页) +- 点击"处理理赔"跳转到理赔处理页面(需关联项目ID) + +**理赔评估明细页内容:** +- 详细信息 +- 相关文档 +- 操作按钮:查看明细、处理理赔 + +#### 2.3.8 智能工具页面(Tools) +**路径**:`/dashboard/tools` + +**权限说明**:所有登录用户可见 + +**工具入口页**: +- 展示三个工具的入口卡片 + - 保费测算工具(所有登录用户可见) + - ICF智能修改(仅投保人可见) + - 方案风险评分(仅投保人可见) +- 每个工具卡片显示:工具名称、工具描述 + +##### 2.3.8.1 保费测算工具(PremiumCalculator) +**路径**:`/dashboard/tools/premium-calculator` + +**权限说明**:所有登录用户可见 + +**功能**: +- 根据项目信息、风险等级等参数计算保费 +- 输入表单字段(待详细设计): + - 项目基本信息 + - 风险等级 + - 保障范围 + - 其他参数 +- 输出:保费计算结果、计算依据说明 + +##### 2.3.8.2 ICF智能修改(ICFEditor) +**路径**:`/dashboard/tools/icf-editor` + +**功能**: +- 智能辅助修改知情同意书(ICF)内容 +- 上传ICF文档 +- 智能识别需要修改的内容 +- 提供修改建议 +- 导出修改后的文档 + +##### 2.3.8.3 方案风险评分(RiskScoring) +**路径**:`/dashboard/tools/risk-scoring` + +**功能**: +- 对试验方案进行风险评分 +- 输入方案信息 +- 自动计算风险评分 +- 显示评分结果、风险等级、改进建议 + +--- + +## 三、核心业务逻辑说明 + +### 3.1 一站式临床试验风险管理模式 + +#### 3.1.1 参与主体 +- **临床试验风险主体**:申办者(研究机构及研究者协助) +- **参与临床试验的主体**:申办者、研究者、研究机构工作人员、CRO、CRC、受试者 + +#### 3.1.2 临床试验风险分类 +**临床试验风险类型**:SUSAR、ADR(SADR)、AE(SAE)、与试验相关非医疗一切风险 + +**风险逻辑:** + +**从临床试验药物角度:** +1. **为证明与药物相关**:AE(不良事件) +2. **已证明与药物相关**:ADR(药物不良反应) + - **SADR(严重药物不良反应)**:严重的ADR + - ADR写入IB的RSI(参考安全信息) +3. **新发现的严重不良反应**:SUSAR(可疑且非预期的严重不良反应),需及时报告 +4. **未写入RSI的严重不良反应属于SUSAR** + +**从临床试验操作角度:** +1. 临床试验方案合理性 +2. 临床试验方案操作是否符合规定 +3. 医疗行为是否合理 +4. 临床试验组织管理是否合理 + +**其他与临床试验有相关性的内容:** +1. 行为原则 +2. 心理原因 +3. 其他 + +#### 3.1.3 赔偿/补偿的内容及基本原则 +1. **最大范围医疗报销原则**(与试验相关的一切风险)发生的必要且合理的医疗支出100%报销。(高频、时效、便捷、最好无垫付) +2. **身故、伤残赔偿金**(低频) +3. **无过错责任补偿金**(低频) +4. **精算损失赔偿金**(低频) + +#### 3.1.4 风险与保险条款的匹配性问题 + +**首要风险:** +- 投保人最关注的厌恶的风险有哪些: + - SUSAR(影响试验走向) + - SAE(经济成本、影响试验进展) + - 非医疗类恶性事件(影响试验进展) +- **特点**:此类风险发生的意愿程度,投保人与保险人完全一致。通过高杠杆方式保险解决 + +**次要风险:** +- AE与试验相关风险 +- **特点**:控制该类风险并非投保人第一关注点(本质上是道德风险),因此容易产生风险敞口,因此保险人在评估风险杠杆承担存在压力和不确定性。 +- **解决方案**:"自保"+"风险减量服务"+"外溢风险管理服务" + +#### 3.1.5 风险管理逻辑模型 + +**基于以上背景,拟提出的风险管理逻辑模型:** +1. **首要风险**:通过高杠杆方式保险解决 +2. **次要风险**:"自保"+"风险减量服务"+"外溢风险管理服务"解决 + +**具体解决方式:** + +**首要风险的责任:** +- SUSAR、SAE造成的身故、残疾赔偿金 +- **强调**:责任明确、金额较大、证据链清晰 +- **方式**:用保险进行风险转移 + +**次要风险的责任:** +- 非SUSAR所发生的医疗费用 +- **方式**:用RMO模式解决 + +### 3.2 RMO模式详细说明 + +#### 3.2.1 模式定位 +RMO模式(也可以换个名字,避免和我们全委托投保的选择冲突) + +**定位**:为投保人解决三件事: +1. "自保"代位执行 +2. 风险减量服务 +3. 外溢风险管理服务 + +#### 3.2.2 合作机构 +- 华泰保险经纪 +- 临研安 +- 其他合作方(X) + +#### 3.2.3 具体操作流程 + +**第一步:风险评估与投保** +- 华泰经纪协助申办者基于项目复杂度和风险度厘清首要风险与次要风险 +- 通过华泰保险经纪完成首要风险投保 + +**第二步:专项风险管理基金设立** +- 申办者根据项目的大小、历史或行业经验,向华泰经纪支付风险管理费采购健康医疗服务(例如一个项目3-5万) +- 双方约定健康医疗服务费的使用范围、对象、执行和审批流程、额度等要素 +- 该费用作为申办者的专项风险管理基金,实行多退少补原则 + +**第三步:费用管理** +- 健康医疗服务成本以医院出具的医疗费用清单为结算凭证 +- **风险管理费采用两种模式:** + - **比例费用**:整体费用的12-15% + - 举例:申办者支付5万元,其中6千元为风险管理费,4.4万计入专项风险管理基金 + - **案件费用**:出现大额、疑难医疗行为与受试者提出额外医疗行为时,针对每个案件收取服务费用(费用结算固定值或者减损比例值) + +**第四步:医疗直付(逐步实现)** +- 华泰力争逐步做到与医院实现直付 +- 华泰长期为日本多家保险公司提供此类服务,日本人在中国的转诊、医疗推荐、费用结算都是华泰完成,此业务已开展近30年 +- 具有外币结算资质 +- 华泰逐步实现和各家医院打通直付,具体额度、时效、范围等还需要再讨论 + +**第五步:出险处理流程** +1. 受试者发生AE或其他医疗需求 +2. 医院第一时间通知华泰 +3. 华泰正常情况下24小时内与医院、受试者沟通,并通知申办者 + - 特殊情况处理:如遇特殊情况,例如受试者非集中、境外、无法联系、不可抗力等,应急处理模式 +4. 安抚受试者、了解医院拟采取的医疗方式及成本 +5. 合理且必要的医疗行为及时安排并承诺受试者救治 +6. 评估申办者专项风险管理基金的余额 +7. 将以上信息报送申办者 +8. 申办者同意使用该基金后,华泰与医院进行结算 +9. 不足部分需申办者补齐,并提供一定费用的余额,以便下次使用 + +**注意事项**:如果真采取这个模式,对申办者内部费用管理可能也是一个挑战,例如康方这么多临床总监和项目估计费用预算都是独立的,康方是设立一个账户还是多个,各个总监的费用应该不能交叉吧,或者康方设立一个总账户类似20w只是应急性共用,总账户垫付后,各个项目在一定时效内及时把垫付的钱补上。 + +**第六步:风险减量服务** +- 华泰经纪与临研安针对临床试验链条的各个环节进行风险点检查 +- 人员培训 +- 方案完善建议 + +**第七步:外溢风险管理服务** +- 针对受试者出险后的全流程管理 +- 联系、安抚、安排就医、沟通诉求、沟通合理预期 + +#### 3.2.4 华泰与临研安的合作模式 +1. 华泰将收到的资金统一进行管理 +2. 临研安在执行中根据申办者的答复从华泰收到相关费用,并含一定比例服务费(8-10%) +3. 华泰逐步实现和各家医院打通直付,具体额度、时效、范围等需要再讨论 +4. 需要临研安经营范围内包括健康服务采购(医疗服务采购不知道是否属于特许经营,只要不涉及具体医疗服务就ok,但是这部分的经营性质、发票等都要考虑细一点) + +### 3.3 风险评估、核保与理赔流程 + +#### 3.3.1 风险评估 +- **AI+人工**:大数据、审查风险点 +- **量化**:各类风险(试验药物+试验流程)频度和程度,如不良反应发生率和救治费用 +- **保险责任设置**:SUSAR(主险必保);其他AE(附加险可保或自保) +- **保单条件设置**:如赔偿限额、免赔额及其设置依据 +- **风险减量服务**:合同管理等 + +#### 3.3.2 核保 +- **完全授权** +- **主动报价+审核** +- **被动报价** +- 基于风险评估及其费率调整因素合理定价 +- 保险条款优化 +- 特殊需求:再保人、共保体 + +#### 3.3.2.1 投保人申请报价流程 + +投保人从多入口发起「获取报价」,经资料填写/上传、AI 生成报价、系统发邮件至保司、保司邮件回复、系统拉取并审核后,将报价回显到页面。流程如下。 + +```mermaid +flowchart TB + subgraph 入口["1. 投保人入口"] + A1[店家首页] + A2[保险方案页] + A3[登录后工作台] + A1 & A2 & A3 --> B[点击「获取报价」] + end + + B --> C[弹出「报价需提交的资料」页面] + + subgraph 资料填写["2. 填写报价资料"] + C --> D{选择填写方式} + D -->|手动填写| E1[填写项目方案编号] + E1 --> E2[填写项目标题] + E2 --> E3[填写申办者] + E3 --> E4[填写项目分期] + D -->|上传方案| F1[上传项目方案文件] + F1 --> F2[AI 识别与解析] + F2 --> F3[自动转化为:方案编号、标题、申办者、分期] + E4 --> G[资料填写完成] + F3 --> G + end + + G --> H[点击「生成报价」] + + subgraph AI报价["3. AI 生成报价"] + H --> I[系统调用 AI 服务] + I --> J[AI 基于资料生成报价] + J --> K[报价结果展示在页面] + end + + K --> L[投保人点击「获取精准报价」] + + subgraph 发送保司["4. 发送至保司"] + L --> M[系统整合报价资料] + M --> N[生成标准化询价邮件/附件] + N --> O[以 Email 发送至各保司] + end + + subgraph 保司回复["5. 保司邮件回复"] + O --> P[各保司评估并报价] + P --> Q[保司通过邮件回复报价] + Q --> R[报价邮件发送至 rmo@vdano.com] + end + + subgraph 系统回收与审核["6. 系统提取与审核"] + R --> S[系统从 rmo@vdano.com 拉取邮件] + S --> T[解析邮件,提取各保司报价] + T --> U[临研安进行报价审核] + U --> V{审核结果} + V -->|通过| W[整理审核通过的报价] + V -->|不通过| X[记录原因,不展示给投保人] + W --> Y[写入报价记录,关联项目] + Y --> Z[返回到报价页面展示] + end + + Z --> END[投保人查看精准报价] +``` + +**流程说明摘要:** + +| 步骤 | 环节 | 说明 | +|------|------|------| +| 1 | 入口 | 投保人可在店家首页、保险方案页或登录后工作台点击「获取报价」。 | +| 2 | 资料 | 弹窗内填写项目方案编号、项目标题、申办者、项目分期;支持手动填写或上传项目方案由 AI 识别并转化为上述字段。 | +| 3 | 生成报价 | 点击「生成报价」后,由 AI 自动生成报价并展示在页面。 | +| 4 | 精准报价 | 点击「获取精准报价」后,系统整合报价资料,以 Email 发送至各保司。 | +| 5 | 保司回复 | 保司通过邮件将报价回复至 rmo@vdano.com。 | +| 6 | 回收与展示 | 系统从 rmo@vdano.com 提取报价,经临研安审核;审核通过的报价整理后回写到报价页面供投保人查看。 | + +#### 3.3.3 理赔 +**SUSAR:** +- 主险必保 +- 抗辩义务+外溢风险管理服务 + +**其他AE:** +- **附加险可保**:保险-高频低效;基础理赔服务(费用核定核算) +- **自保**:第三方健康医疗服务(专项基金费用管理) + - **被动型**:AE发生-医生救治-费用单列-开票结算支付(申办方或第三方) + - **主动型**:AE发生-医生救治+医嘱购药-第三方采购服务-基金结算 + +**风险减量服务**:根据统计分析、风险提示、合同争议等优化合同和流程。 + +**利益冲突**:申办方&保险人;完善保险条款和理赔指引,减少合同纠纷和利益冲突。 + +--- + +## 四、功能需求 + +### 4.1 用户认证与权限 + +#### 4.1.1 免登录浏览区(已实现) +- **访问方式**:网站免登录访问,所有内容公开 +- **页面范围**:首页、风险职责、临床试验、上市应用、海外风险、资源中心、常见问题 + +#### 4.1.2 登录后系统(待开发) +- **登录方式**:账号、密码登录 +- **用户角色**:投保人、保险人 +- **权限控制**:根据用户角色呈现有权限的内容 +- **路由守卫**:未登录用户访问登录后页面应重定向到登录页 +- **状态管理**:需要全局状态管理用户信息、角色权限、登录状态(建议使用 Context API 或 Zustand) + +#### 4.1.3 数据权限说明 +- **数据权限维度**: + 1. **所在的法人组织(企业信用代码)** + - 申办者:CRA、采购、CRC、研究者均以申办者身份登录 + - 经纪公司、保司:以法人组织身份登录 + 2. **临床试验项目(保单号)** + - 用户只能查看和操作自己所在法人组织下的项目 + - 保险人只能查看和操作分配给自己的项目 +- **项目状态**:信息收集、询价中、已报价、已成交、已失效 + +### 4.2 登录后系统功能模块(待开发) + +#### 4.2.1 工作台(Dashboard) +- **路径**:`/dashboard` +- **布局**:侧边栏导航 + 主内容区(DashboardLayout) +- **权限**:所有登录用户可见,但内容根据角色不同 + +**投保人工作台内容模块**: + - **数据统计卡片**: + - 询价项目:显示询价项目数量,点击后进入询价项目列表 + - 生效保障:显示生效保障数量,点击后进入生效保障列表 + - 全部项目:显示全部项目数量,点击后进入全部项目列表(包括生效中及已失效的项目) + - **待处理任务**: + - 待处理的报价项目(状态含:资料填写中、已生成报价、询价中、已回显精准报价等) + - 待处理的理赔 + - **快捷方式**(页面右上角): + - 获取报价:打开报价申请流程(弹窗或报价页面,见 2.3.2.3) + - 申请理赔:跳转到理赔申请页面(需关联项目) + - 方案介绍:申请设计并介绍方案 + - 培训支持:申请培训支持 + +**保险人工作台内容模块**: + - **数据统计卡片**: + - 询价项目:显示询价项目数量,点击后进入询价项目列表 + - 生效保障:显示生效保障数量,点击后进入生效保障列表 + - 全部项目:显示全部项目数量,点击后进入全部项目列表(包括生效中及已失效的项目) + - **待处理任务**: + - 待处理的报价项目 + - 待处理的理赔 + +#### 4.2.2 报价申请(获取报价) +- **入口**:首页 Banner、保险方案页、登录后工作台「获取报价」 +- **形式**:弹窗或独立页面(建议弹窗);对应业务流程图见 3.3.2.1 +- **功能要点**: + - 报价资料表单:项目方案编号、项目标题、申办者、项目分期;支持手动填写或上传项目方案由 AI 识别并自动填充 + - 生成报价:调用 AI 生成报价并展示 + - 获取精准报价:系统整合资料后以 Email 发送至各保司;前端展示「询价中」等状态 + - 精准报价回显:系统从 rmo@vdano.com 拉取保司回复,临研安审核通过后,报价回写到该次申请,前端在报价页面/报价详情展示 +- **投保人**:可在工作台「待处理的报价项目」中查看各次申请状态并进入报价详情查看精准报价 + +#### 4.2.3 项目列表(ProjectList) +- **路径**:`/dashboard/projects` +- **权限**:仅投保人可见 +- **列表字段**: + - 项目编号 + - 试验题目 + - 保障范围 + - 承保公司 + - 承保状态(如:已承保、待审核、已过期等) + - 操作按钮:查看明细、申请理赔 +- **交互**: + - 点击"查看明细"跳转到 `/dashboard/projects/:id`(项目明细页) + - 点击"申请理赔"跳转到理赔申请页面(需关联项目ID) + +#### 4.2.4 项目明细(ProjectDetail) +- **路径**:`/dashboard/projects/:id` +- **权限**:仅投保人可见 +- **内容**:项目详细信息展示 + - 基本信息:项目编号、题目、申办者、保障范围等 + - 保障信息:承保公司、承保状态、保障期限、保障金额等 + - 相关文档:保险合同、评估报告等(可下载或预览) + - 操作按钮:查看明细、申请理赔 + +#### 4.2.5 询价列表(InquiryList) +- **路径**:`/dashboard/inquiries` +- **权限**:仅保险人可见 +- **列表字段**: + - 申办者 + - 项目编号 + - 试验题目 + - 保障范围 + - 特别约定 + - 成交情况(如:已成交、待确认、已拒绝等) +- **交互**:可查看评估详情、处理报价等 + +#### 4.2.6 理赔进度(ClaimProgress) +- **路径**:`/dashboard/claims` +- **权限**:投保人、保险人可见 +- **投保人理赔进度**: + - 列表字段:项目编号、试验题目、保障范围、承保公司、承保状态 + - 操作按钮:查看明细、申请理赔 +- **保险人理赔进度**: + - 列表字段:项目编号、试验题目、保障范围、承保公司、承保状态 + - 操作按钮:查看明细、处理理赔 + +#### 4.2.7 智能工具(Tools) +- **路径**:`/dashboard/tools` +- **权限**:所有登录用户可见 +- **工具入口页**:展示三个工具的入口卡片 + - 保费测算工具(所有登录用户可见) + - ICF智能修改(仅投保人可见) + - 方案风险评分(仅投保人可见) + +##### 4.2.7.1 保费测算工具(PremiumCalculator) +- **路径**:`/dashboard/tools/premium-calculator` +- **权限**:所有登录用户可见 +- **功能**:根据项目信息、风险等级等参数计算保费 + +##### 4.2.7.2 ICF智能修改(ICFEditor) +- **路径**:`/dashboard/tools/icf-editor` +- **功能**:智能辅助修改知情同意书(ICF)内容 + +##### 4.2.7.3 方案风险评分(RiskScoring) +- **路径**:`/dashboard/tools/risk-scoring` +- **功能**:对试验方案进行风险评分 + +### 4.3 内容管理(部分已实现) +- **内容发布**:支持发布活动动态、资源文件(待实现后台管理) +- **内容分类**:支持多级分类管理(资源中心已实现分类展示) +- **内容搜索**:支持全文搜索功能(待实现) +- **文件管理**:支持文档上传、下载、预览(待实现) + +### 4.4 交互功能(待开发) +- **在线咨询**:支持在线咨询功能(未来扩展) +- **表单提交**:支持服务申请表单提交(登录后系统);报价申请支持手动填写与上传项目方案(AI 识别填充) +- **流程跟踪**:支持查看服务流程状态(登录后系统);报价申请支持状态跟踪(资料填写中、已生成报价、询价中、已回显精准报价)及精准报价结果展示 +- **邮件与回显**:获取精准报价时系统发送邮件至保司;系统从 rmo@vdano.com 拉取保司回复,经临研安审核后回写并展示在报价页面 + +### 4.5 数据可视化(部分已实现) +- **模式图展示**:风险管理体系图示(首页已实现) +- **流程图展示**:服务流程可视化(保证方案页面已实现保证基金逻辑图) +- **数据统计**:服务数据统计展示(登录后系统工作台待实现) + +--- + +## 五、非功能需求 + +### 5.1 性能要求 +- 页面加载速度:首屏加载时间<3秒 +- 响应时间:交互响应时间<1秒 +- 支持并发用户数:根据实际需求确定 + +### 5.2 安全要求 +- 数据加密传输(HTTPS) +- 用户数据安全保护 +- 访问日志记录 +- 权限控制(如需要) + +### 5.3 兼容性要求 +- 浏览器兼容:Chrome、Firefox、Safari、Edge最新版本 +- 响应式设计:支持PC、平板、手机访问 +- 分辨率适配:1920x1080、1366x768、移动端等 + +### 5.4 可维护性要求 +- 代码规范:遵循前端开发规范 +- 文档完善:提供技术文档和用户手册 +- 易于扩展:支持后续功能扩展 + +--- + +## 六、设计建议 + +### 6.1 视觉设计 +- **风格定位**:专业、可信、现代 +- **色彩方案**:医疗健康相关配色,体现专业性 +- **图标系统**:统一的图标风格 +- **品牌元素**:临研安、华泰保险经纪品牌元素融入 + +### 6.2 用户体验 +- **导航清晰**:清晰的导航结构,易于找到信息 +- **信息层次**:重要信息突出展示 +- **交互友好**:操作简单直观 +- **内容可读性**:文字排版清晰,易于阅读 + +### 6.3 特殊页面设计 +- **模式图页面**:重点设计,支持交互式展示 +- **流程页面**:流程图清晰展示各步骤 +- **资源中心**:支持分类浏览、搜索、下载 + +--- + +## 七、技术建议 + +### 7.1 技术栈(已采用) +- **前端框架**:React 18.2.0 ✅ +- **语言**:TypeScript 5.2.2 ✅ +- **构建工具**:Vite 5.0.8 ✅ +- **路由**:react-router-dom 6.20.0 ✅ +- **样式**:原生 CSS + CSS 变量 ✅ +- **UI组件库**:无(使用原生 CSS 实现)✅ + +### 7.2 登录后系统技术栈建议(待实现) +- **状态管理**:Context API 或 Zustand(推荐 Zustand,简单易用) +- **数据请求**:axios + React Query 或 SWR(用于服务端数据缓存、同步、重试) +- **路由守卫**:ProtectedRoute 组件(检查登录状态) +- **权限控制**:基于角色的权限控制(RoleGuard 组件或 HOC) +- **表单管理**:React Hook Form(如需要复杂表单) + +### 7.3 其他技术建议 +- **图表库**:ECharts / D3.js(用于模式图可视化,如需要) +- **文件预览**:@vue-office 或类似库(用于文档预览) + +### 7.2 后端建议(未来扩展) +- **后端框架**:Node.js / Python Django / Java Spring Boot +- **数据库**:MySQL / PostgreSQL +- **文件存储**:OSS / 本地存储 +- **API设计**:RESTful API + +### 7.3 部署建议 +- **服务器**:云服务器(阿里云/腾讯云等) +- **CDN**:静态资源CDN加速 +- **域名SSL**:配置HTTPS证书 + +--- + +## 八、开发阶段建议 + +### 8.1 第一阶段:免登录浏览区(已完成 ✅) +1. ✅ 用户访问系统(免登录) +2. ✅ 首页(Banner、风险管理体系、角色专区) +3. ✅ 风险职责模块(总览、申办者、持有人、研究中心、受试者、CXO) +4. ✅ 临床试验模块(保险方案、保证方案、保险保证) +5. ✅ 上市应用、海外风险页面 +6. ✅ 资源中心(实践指南、培训材料、常见问题) +7. ✅ 登录页面(UI已实现,登录逻辑待开发) + +### 8.2 第二阶段:登录后系统(待开发) +1. ⏳ 用户认证与权限系统 + - 登录接口对接 + - 全局状态管理(AuthContext) + - 路由守卫(ProtectedRoute) + - 角色权限控制 +2. ⏳ 工作台页面 + - 我的保障、我的项目、待办任务 + - 快捷方式(获取报价、申请保障、申请理赔) + - 待处理的报价项目(含状态与跳转报价详情) +3. ⏳ 报价申请流程(获取报价) + - 多入口(首页、保险方案页、工作台)打开报价弹窗/页面 + - 报价资料表单(手动填写 + 上传方案 AI 识别)、生成报价、获取精准报价 + - 报价状态与精准报价回显(系统拉取 rmo@vdano.com、临研安审核后展示) +4. ⏳ 项目列表模块 + - 项目列表(带权限过滤) + - 项目明细页 +5. ⏳ 询价列表模块(保险人) + - 询价列表(需权限控制) + - 询价详情、处理报价 +6. ⏳ 理赔进度模块(投保人、保险人) + - 理赔评估列表(需权限控制) + - 理赔详情、处理理赔 +7. ⏳ 智能工具模块(所有登录用户可见) + - 保费测算工具(所有登录用户可见) + - ICF智能修改(仅投保人可见) + - 方案风险评分(仅投保人可见) +8. ⏳ 登录后系统布局 + - DashboardLayout(侧边栏 + 主内容区) + - 侧边栏导航(根据角色动态显示) + +### 8.3 第三阶段:功能增强(未来扩展) +1. 在线咨询功能 +2. 理赔申请流程 +3. 数据统计展示 +4. 后台管理系统(内容管理、用户管理) +5. 文件上传下载功能 +6. 全文搜索功能 + +--- + +## 九、待确认事项 + +### 9.1 已确认事项 +1. ✅ **登录系统**:账号密码登录,无需注册功能(用户由管理员创建) +2. ✅ **路由架构**:免登录浏览区 + 登录后系统区,使用路由守卫控制访问 +3. ✅ **权限控制**:基于角色的权限控制,2种用户角色(投保人、保险人) + +### 9.2 待确认事项 +1. **费用管理**:申办方内部费用管理系统的集成需求(如康方案例中提到的问题) +2. **直付功能**:医院直付功能的具体实现方式和时间表 +3. **经营资质**:临研安健康服务采购的经营资质确认 +4. **发票管理**:服务费用的发票开具方式 +5. **多账户管理**:申办方多项目账户管理方案(总账户+项目账户模式) +6. **资源中心**:是否需要实现真实的文件上传下载功能 +7. **活动动态**:是否需要后台管理系统来管理活动内容 +8. **API接口**:登录后系统的后端API接口设计(接口文档、数据格式等) +9. **数据权限**:项目列表、保障评估等数据的权限控制规则细节 +10. **智能工具**:保费测算、ICF修改、风险评分等工具的具体算法和实现方式 + +--- + +## 十、附录 + +### 10.1 术语表 +- **RMO**:Risk Management Organization,风险管理组织 +- **AE**:Adverse Event,不良事件 +- **ADR**:Adverse Drug Reaction,药物不良反应 +- **SADR**:Serious Adverse Drug Reaction,严重药物不良反应 +- **SAE**:Serious Adverse Event,严重不良事件 +- **SUSAR**:Suspected Unexpected Serious Adverse Reaction,可疑且非预期的严重不良反应 +- **IB**:Investigator's Brochure,研究者手册 +- **RSI**:Reference Safety Information,参考安全信息 +- **CRO**:Contract Research Organization,合同研究组织 +- **CDMO**:Contract Development and Manufacturing Organization,合同开发与生产组织 +- **SMO**:Site Management Organization,现场管理组织 + +### 10.2 参考文件 +- 原需求文档:`临床试验保险及RMO模式网站设计思路.md` +- 相关图片资源:申办者和持有人责任风险管理相关图片 + +### 10.3 项目状态 +- **当前版本**:v1.1 +- **开发状态**: + - ✅ 免登录浏览区:已完成,可演示 + - ⏳ 登录后系统:待开发 +- **最后更新**:2025年2月 + +--- + +**文档版本**:v1.1 +**创建日期**:2025年1月 +**最后更新**:2025年2月 + +### 10.4 开发状态说明 +- **✅ 已实现**:功能已开发完成,需求描述与代码实现一致 +- **⏳ 待开发**:功能规划已完成,待开发实现 +- **未来扩展**:功能需求已明确,优先级较低,后续版本实现 + + + diff --git a/ReferenceBook/Acceptable-Ranges-and-Good-Clinical-Practice-ICH-E6-R3_Final_V1.0.pptx b/ReferenceBook/Acceptable-Ranges-and-Good-Clinical-Practice-ICH-E6-R3_Final_V1.0.pptx new file mode 100644 index 0000000..3c1f5db Binary files /dev/null and b/ReferenceBook/Acceptable-Ranges-and-Good-Clinical-Practice-ICH-E6-R3_Final_V1.0.pptx differ diff --git a/ReferenceBook/CTTI_QbD_Monitoring_Woodcock.pdf b/ReferenceBook/CTTI_QbD_Monitoring_Woodcock.pdf new file mode 100644 index 0000000..aacd085 Binary files /dev/null and b/ReferenceBook/CTTI_QbD_Monitoring_Woodcock.pdf differ diff --git a/ReferenceBook/Control-Strategies-and-Good-Clinical-Practice-ICH-E6-R3_20240708.pptx b/ReferenceBook/Control-Strategies-and-Good-Clinical-Practice-ICH-E6-R3_20240708.pptx new file mode 100644 index 0000000..4460c2b Binary files /dev/null and b/ReferenceBook/Control-Strategies-and-Good-Clinical-Practice-ICH-E6-R3_20240708.pptx differ diff --git a/ReferenceBook/DAC-Assessment-Tool-DAT_2023_vPublic_FINAL.pdf b/ReferenceBook/DAC-Assessment-Tool-DAT_2023_vPublic_FINAL.pdf new file mode 100644 index 0000000..3a097fc Binary files /dev/null and b/ReferenceBook/DAC-Assessment-Tool-DAT_2023_vPublic_FINAL.pdf differ diff --git a/ReferenceBook/Example_1_Type_A_Protocol_Synopsis.pdf b/ReferenceBook/Example_1_Type_A_Protocol_Synopsis.pdf new file mode 100644 index 0000000..b82b7e4 Binary files /dev/null and b/ReferenceBook/Example_1_Type_A_Protocol_Synopsis.pdf differ diff --git a/ReferenceBook/Example_1_Type_A_Risk_Assessment.pdf b/ReferenceBook/Example_1_Type_A_Risk_Assessment.pdf new file mode 100644 index 0000000..ab10cd7 Binary files /dev/null and b/ReferenceBook/Example_1_Type_A_Risk_Assessment.pdf differ diff --git a/ReferenceBook/Example_2_Type_A_Protocol_Synopsis.pdf b/ReferenceBook/Example_2_Type_A_Protocol_Synopsis.pdf new file mode 100644 index 0000000..8729925 Binary files /dev/null and b/ReferenceBook/Example_2_Type_A_Protocol_Synopsis.pdf differ diff --git a/ReferenceBook/Example_2_Type_A_Risk_Assessment.pdf b/ReferenceBook/Example_2_Type_A_Risk_Assessment.pdf new file mode 100644 index 0000000..282011d Binary files /dev/null and b/ReferenceBook/Example_2_Type_A_Risk_Assessment.pdf differ diff --git a/ReferenceBook/Example_3_Type_B_Protocol_Synopsis.pdf b/ReferenceBook/Example_3_Type_B_Protocol_Synopsis.pdf new file mode 100644 index 0000000..a1440d0 Binary files /dev/null and b/ReferenceBook/Example_3_Type_B_Protocol_Synopsis.pdf differ diff --git a/ReferenceBook/Example_3_Type_B_Risk_Assessment.pdf b/ReferenceBook/Example_3_Type_B_Risk_Assessment.pdf new file mode 100644 index 0000000..e9c26f8 Binary files /dev/null and b/ReferenceBook/Example_3_Type_B_Risk_Assessment.pdf differ diff --git a/ReferenceBook/GCP 2025 征求意见.md b/ReferenceBook/GCP 2025 征求意见.md new file mode 100644 index 0000000..2d79004 --- /dev/null +++ b/ReferenceBook/GCP 2025 征求意见.md @@ -0,0 +1,774 @@ +第一章 总 则 + + + +第一条【目的】 为保证药物临床试验过程规范,保护试验参与者的权益、安全和福祉,确保数据和结果科学、真实、可靠,根据《中华人民共和国药品管理法》《中华人民共和国疫苗管理法》《中华人民共和国药品管理法实施条例》《药品注册管理办法》,制定本规范。 + + + +第二条【适用范围】 本规范适用于经国务院药品监督管理部门批准或备案,为申请药品注册而进行的药物临床试验。药物临床试验的相关活动应当遵守本规范。 + + + +第三条【涵盖范围】 药物临床试验质量管理规范是药物临床试验全过程应当遵循的伦理、科学和质量的标准。药物临床试验全过程包括计划、启动、执行、记录、监督、评估、分析和报告。 + + + +第四条【遵循赫尔辛基宣言原则】 药物临床试验应当符合《世界医学大会赫尔辛基宣言》原则及相关伦理要求,试验参与者的权益、安全和福祉是考虑的首要因素,优先于对科学和社会的获益。伦理审查与知情同意是保障试验参与者权益和福祉的重要措施。 + + + +第五条【临床试验的科学性原则及获益原则】 药物临床试验应当科学合理,权衡试验参与者和社会的预期风险和获益,只有当预期的获益大于风险时,方可实施或者继续临床试验。 + + + +第六条【试验设计理念、风险相称原则】 临床试验的设计和实施应当纳入质量源于设计的方法,识别试验的关键质量因素及相关风险,并采用与之相称的风险控制措施,保护试验参与者的权益和安全,确保结果可靠。 + + + +第七条【临床试验方案】 临床试验方案应当清晰、简明、科学合理、可操作,在获得伦理审查委员会批准后方可执行。临床试验实施过程中,为确保其科学性和伦理性,必要时可调整试验方案,并再次获得伦理审查委员会批准后方可执行。 + + + +第八条【研究人员资质原则】 参与临床试验的各方人员应当具有能够承担临床试验工作相应的教育背景、培训经历和实践经验,在临床试验过程中应当遵守试验方案。凡涉及医学判断或临床决策应当由临床医生做出。 + + + +第九条【数据记录处理保存原则】 所有临床试验的纸质或电子资料应当被妥善地记录、处理和保存,保证数据的可靠性和可追溯性。应当保护试验参与者的隐私和个人信息的安全,符合我国关于个人信息保护的有关要求。 + +用于数据采集、管理、分析的系统和流程应当符合预期目的,并与对试验参与者的风险和所采集数据的重要性相称。 + + + +第十条【试验用药品原则】 试验用药品的制备应当符合临床试验用药品生产质量管理相关要求。试验用药品的使用和管理应当符合试验方案和相关法律法规要求。 + + + +第十一条【质量管理原则】 药物临床试验的质量管理应当贯穿临床试验全过程,以保护试验参与者的权益和安全,确保临床试验结果可靠,并遵守相关法律法规。 + + + +第十二条【利益冲突回避原则】 药物临床试验各方应当对可能存在的利益冲突采取回避、控制等管理措施,避免对试验参与者保护及试验结果的可靠性产生影响。 + + + +第十三条【使用新技术原则】 应用新技术、新方法开展药物临床试验,应当符合伦理、科学和相关法律法规要求。 + + + +第二章 伦理审查委员会 + + + +第十四条【职责】 伦理审查委员会的职责是保护试验参与者的权益、安全和福祉。伦理审查委员会应当对临床试验的科学性和伦理性进行审查,并特别关注弱势试验参与者的保护。伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定,以及药品监督管理部门的监管要求。 + + + +(一)【审查文件清单】 伦理审查委员会审查的文件包括:试验方案、知情同意书、招募试验参与者的方式和信息、提供给试验参与者的其他书面资料、研究者手册、安全性资料、包含试验参与者补偿信息的文件、主要研究者资质证明文件、重要方案偏离报告、总结报告,以及伦理审查委员会履行其职责所需要的其他文件等。 + + + +(二)【特殊情况下的伦理审查】 伦理审查委员会应当特别关注以下特殊情况,审查对试验参与者的权益、安全和福祉保护是否充分: + +试验参与者在没有预期获益的临床试验中,知情同意是由其法定代理人替代实施。 + +试验参与者为无民事行为能力和限制民事行为能力人员。 + +涉及未成年人,伦理审查委员会应当审查针对未成年人的知情同意信息,并综合考虑试验参与者的年龄特征、认知成熟度、心理状态以及适用的法规要求。 + +试验方案中明确说明紧急情况下试验参与者或者其法定代理人无法在试验前签署知情同意书。 + + + +(三)【防范胁迫与免责】 伦理审查委员会应当审查是否存在强迫、利诱等方式影响试验参与者参加临床试验的情形。伦理审查委员会应当审查知情同意书中是否存在要求试验参与者或者其法定代理人放弃合法权益的内容,也不得含有免除主要研究者、临床试验机构、申办者及其代理机构应当承担责任的内容。 + + + +(四)【补偿机制】 伦理审查委员会应当确保知情同意书、提供给试验参与者的其他书面资料说明了给试验参与者补偿的信息,包括补偿方式、数额和计划。 + + + +(五)【安全性事件审查】 伦理审查委员会应当重点关注并及时审查以下情况:临床试验中发生主要研究者报告的严重不良事件,临床试验实施过程中为消除对试验参与者紧急危害的试验方案的偏离或者修改,严重、持续的不依从问题,增加试验参与者风险或者显著影响临床试验实施的改变,可能对试验参与者的安全或者临床试验的实施产生不利影响的新信息,其他潜在的严重安全性风险信息。 + +对申办者报告的可疑且非预期的严重不良反应,以及药物研发期间安全性更新报告,伦理审查委员会审查的方式至少应当与需要采取措施的紧迫性和试验用药品安全性特征的变化相称。 + + + +(六)【跟踪审查】 伦理审查委员会应当对正在实施的临床试验定期跟踪审查,审查的频率应当根据试验参与者的风险程度而定,时间间隔不超过12个月。 + + + +(七)【审查意见类型和内容】 伦理审查委员会应当在合理的时限内完成临床试验相关资料的审查或者备案流程,并给出明确的书面审查意见。伦理审查委员会的审查意见有:批准、不批准、修改后批准、修改后再审、继续研究、暂停或者终止研究的决定。 + + + +(八)【暂停/终止试验】 伦理审查委员会有权暂停、终止未按照相关要求实施,或者试验参与者出现非预期严重损害的临床试验。 + + + +(九)【受理试验参与者诉求】 伦理审查委员会应当受理并妥善处理试验参与者的相关诉求。 + + + +第十五条【组成运行要求】 伦理审查委员会应当建立伦理审查工作制度、标准操作规程,健全利益冲突管理机制和伦理审查质量控制机制,保证伦理审查过程独立、客观、公正。 + + + +第十六条【文件保存】 伦理审查委员会应当保留伦理审查的全部记录,包括伦理审查的书面记录、委员信息、递交的文件、会议记录和相关往来记录等。用于申请药品注册的临床试验,所有记录应当至少保存至试验药物被批准上市后5年;未用于申请药品注册的临床试验,应当至少保存至临床试验终止后5年。 + + + +第十七条【透明化要求】 伦理审查委员会应当向主要研究者和申办者提供相关书面记录,包括伦理审查委员会名称和地址、参与项目审查的伦理审查委员会委员名单、审查的书面意见、符合本规范及相关法律法规的审查声明等。必要时,主要研究者、申办者或者药品监督管理部门可以要求伦理审查委员会提供其标准操作规程。 + + + +第三章 主要研究者和药物临床试验机构 + + + +第十八条【临床试验机构资质要求】 药物临床试验机构开展药物临床试验,应当建立药物临床试验质量管理体系,并确保其有效运行。 + + + +第十九条【主要研究者资质及要求】 主要研究者是临床试验现场的最终责任人,对试验参与者权益、安全及临床试验质量负责。主要研究者应当具备的资质和要求包括: + + + +(一)【资质】 具有在临床试验机构的相应执业资格,具备临床试验所需的教育背景、培训经历和实践经验。 + + + +(二)【要求】 熟悉申办者提供的试验方案、研究者手册、试验药物相关资料信息。 + + + +(三)【要求】 熟悉临床试验相关技术指南,并遵守本规范及相关法律法规。 + + + +第二十条【服务外包】 主要研究者和临床试验机构应当建立完整的工作制度以确保其履行临床试验相关职责和功能。如果授权个人或者单位承担临床试验的相关职责和功能,主要研究者和临床试验机构应当确保其具备相应资质,并对其进行适当监督。 + + + +第二十一条【开展试验必要条件】 主要研究者和临床试验机构应当具有完成临床试验所需的必要条件: + + + +(一)【入组试验参与者的时间和能力】 主要研究者在临床试验协议约定的期限内有足够的时间和能力入组足够数量且符合试验方案的试验参与者。 + + + +(二)【完成试验的软硬件】 主要研究者具有使用临床试验所需设施的权限,有权支配并监管参与临床试验的研究人员,正确、安全地实施临床试验。 + + + +第二十二条【与伦理审查委员会沟通】 主要研究者与伦理审查委员会的沟通包括: + + + +(一)临床试验实施前,主要研究者应当获得伦理审查委员会批准;未获得伦理审查委员会批准前,不能筛选试验参与者。 + + + +(二)临床试验实施前、过程中和结束后,主要研究者应当按要求向伦理审查委员会报告,并提供伦理审查需要的所有文件。 + + + +(三)主要研究者应当及时执行伦理审查委员会的审查意见。 + + + +第二十三条【遵守方案】 主要研究者应当遵守试验方案。 + + + +(一)【方案偏离】 主要研究者应当按照伦理审查委员会批准的试验方案实施临床试验。如偏离试验方案,主要研究者或者其授权的研究人员应当记录和解释,必要时采取适当的纠正和预防措施,并及时向伦理审查委员会报告。 + + + +(二)【紧急危害下的方案偏离】 为消除对试验参与者的紧急危害,在未获得伦理审查委员会同意的情况下,主要研究者修改或者偏离试验方案,应当及时向伦理审查委员会、申办者报告,并说明理由。 + + + +(三)【紧急揭盲】 主要研究者应当按照试验方案的要求实施揭盲。如意外破盲或者紧急揭盲,主要研究者应当立即记录,并向申办者书面说明原因。 + + + +第二十四条【提前终止或者暂停临床试验】 临床试验提前终止或者暂停,主要研究者应当及时通知试验参与者,并给予试验参与者适当的治疗和随访。主要研究者、申办者或伦理审查委员会提前终止或暂停临床试验,主要研究者应当立即向申办者、伦理审查委员会中的非发起方报告,同时报告临床试验机构,并提供书面说明。 + + + +第二十五条【医疗照顾】 主要研究者为临床医生或者由其授权的临床医生应当给予试验参与者适合的医疗处理,并承担与临床试验有关的医学决策责任。 + + + +第二十六条【安全性报告】 主要研究者的安全性报告应当符合以下要求: + + + +(一)【不良事件】 主要研究者应当按照试验方案的要求和时限向申办者报告安全性评价所需的不良事件和/或异常检查结果。 + + + +(二)【严重不良事件】 除试验方案或者其他文件(如研究者手册)中规定不需立即报告的严重不良事件外,主要研究者应当在获知后立即向申办者和伦理审查委员会书面报告所有严重不良事件,随后应当及时提供详尽、书面的随访报告。 + + + +(三)【死亡事件】 涉及死亡事件报告,如申办者、伦理审查委员会、药品监督管理部门要求提供其他所需要的资料,如尸检报告和最终医学报告等,主要研究者应当在获得后及时提供。 + + + +(四)【可疑且非预期严重不良反应(SUSAR)、其他潜在的严重安全性风险信息、药物研发期间安全性更新报告(DSUR)】 主要研究者收到申办者提供的可疑且非预期严重不良反应、其他潜在的严重安全性风险信息、药物研发期间安全性更新报告等安全性信息后应当及时签阅,并应考虑试验参与者的治疗是否进行相应调整,必要时尽早与试验参与者沟通。 + + + +第二十七条【知情同意】 主要研究者实施知情同意应当遵守赫尔辛基宣言的伦理原则,确保试验参与者是自愿参加临床试验,并通过知情同意过程确保其获知充分的信息: + + + +(一)【获得知情同意】 参加临床试验前,主要研究者应当充分告知试验参与者有关临床试验的相关事宜,获得试验参与者的知情同意并记录,使用经伦理审查委员会批准的最新版知情同意书和其他提供给试验参与者的信息。 + + + +(二)【获知新信息告知试验参与者】 主要研究者获得可能影响试验参与者继续参加临床试验的新信息时,应当及时告知试验参与者或者其法定代理人,并作相应记录。如有必要,应当再次签署知情同意书。 + + + +(三)【不得强迫、利诱】 研究人员不得采用强迫、利诱等不正当的方式影响试验参与者参加或者继续参加临床试验。 + + + +(四)【知情同意材料通俗易懂】 知情同意书等提供给试验参与者的资料应当采用通俗易懂的语言和表达方式,使试验参与者或者其法定代理人、见证人易于理解。 + + + +(五)【试验参与者充分考虑】 签署知情同意书之前,主要研究者或者经其授权的研究人员应当给予试验参与者或者其法定代理人充分的时间和机会了解临床试验的详细情况,并详尽回答试验参与者或者其法定代理人提出的与临床试验相关的问题。 + + + +(六)【知情同意签字】 试验参与者或者其法定代理人,以及执行知情同意的研究人员应当在知情同意书上分别签名并注明日期,如非试验参与者本人签署,应当注明关系。病史记录中应当记录试验参与者知情同意的具体时间和人员。 + + + +(七)【缺乏阅读能力时的知情同意】 如试验参与者或者其法定代理人缺乏阅读能力,应当有一位公正的见证人见证整个知情同意过程。应当向试验参与者或者其法定代理人、见证人详细说明知情同意书和其他文字资料的内容。如试验参与者或者其法定代理人口头同意参加临床试验,在有能力情况下应当尽量签署知情同意书,见证人还应当在知情同意书上签字并注明日期,以证明试验参与者或者其法定代理人就知情同意书和其他文字资料得到了准确地解释,并理解了相关内容,同意参加临床试验。 + + + +(八)【获得知情同意副本】 试验参与者或者其法定代理人应当得到已签署姓名和日期的知情同意书原件或者副本和其他提供给试验参与者的资料,以及后续更新版本。 + + + +(九)【无民事行为、限制民事行为能力的知情同意】 试验参与者为无民事行为能力人的,应当取得其法定代理人的书面知情同意;试验参与者为限制民事行为能力人的,应当取得本人及其法定代理人的书面知情同意。当法定代理人代表试验参与者知情同意时,应当在试验参与者可理解的范围内告知试验参与者临床试验的相关信息,并尽量让试验参与者亲自签署知情同意书并注明日期。 + +未成年人作为试验参与者,应当征得其法定代理人的知情同意并签署知情同意书。当未成年人有能力做出同意参加临床试验的决定时,还应当征得其本人同意,如果未成年试验参与者本人不同意参加临床试验或者中途决定退出临床试验时,即使法定代理人已经同意参加或者愿意继续参加,也应当以未成年试验参与者本人的决定为准;除非在严重或者危及生命疾病的治疗性临床试验中,主要研究者、其法定代理人认为未成年试验参与者如不参加临床试验其生命会受到危害,这时其法定代理人的同意即可使试验参与者参加或者继续参加;在临床试验过程中,未成年试验参与者达到了签署知情同意的条件,则需要由本人签署知情同意之后方可继续实施。 + +限制民事行为能力人在恢复民事行为能力后,需要对其再次知情同意,获得其本人的自愿同意继续或是退出相关临床试验。 + + + +(十)【紧急情况下的知情同意】 紧急情况下,参加临床试验前不能获得试验参与者的知情同意时,其法定代理人可以代表试验参与者知情同意,若其法定代理人也不在场时,试验参与者的入选方式应当在试验方案以及其他文件中清楚表述,并获得伦理审查委员会的书面同意,同时应当尽快得到试验参与者或者其法定代理人可以继续参加临床试验的知情同意。 + + + +(十一)【无预期获益的知情同意】 试验参与者参加没有预期获益的临床试验,原则上应当由试验参与者本人签署知情同意。 + + + +第二十八条【药品管理】 主要研究者和临床试验机构对申办者提供的试验用药品负有管理责任。 + + + +(一)【专人管理及管理环节】 主要研究者和临床试验机构应当指派具备相应资质的专门人员管理试验用药品,临床试验机构接收、处理、贮存、分发、使用、回收及退还试验用药品应当遵守相应的规定并保存记录。主要研究者应当确保试验用药品按照试验方案使用,并向试验参与者说明试验用药品的正确使用方法。 + + + +(二)【生物等效性试验用药品留样】 主要研究者应当对生物等效性试验的临床试验用药品进行随机抽取留样,至少保存留样至药品上市后2年,并制定相应管理制度。临床试验机构可将留存样品委托具备条件的独立的第三方保存,但不得返还申办者或者与其利益相关的第三方。 + + + +第二十九条【记录和报告】 临床试验的记录和报告应当符合以下要求: + + + +(一)【监督履职确保数据可靠】 主要研究者应当监督试验现场的数据采集、各研究人员履行其工作职责的情况,确保数据的可靠性。 + + + +(二)【临床试验数据记录、修改、溯源、保存】 主要研究者应当确保所有临床试验数据是从临床试验的源记录中获得的,并保证其可靠性和可追溯性。源记录应当具有可归因性、易读性、同时性、原始性、准确性和完整性。源记录的修改应当留痕,不能掩盖初始数据,并记录修改的理由。以患者为试验参与者的临床试验,相关的医疗记录应当载入门诊或者住院病历系统。 + + + +(三)【文件保存年限】 主要研究者和临床试验机构应当按照药品监督管理部门的相关管理要求,妥善保存试验文档。用于申请药品注册的临床试验,临床试验必备记录应当至少保存至试验药物被批准上市后5年;未用于申请药品注册的临床试验,必备记录应当至少保存至临床试验终止后5年。 + + + +(四)【个人信息保护】 在临床试验数据和试验参与者信息处理过程中应当注意避免非法或者未授权的收集、存储、使用、更正、传输、提供、公开、删除等。临床试验数据的记录、处理和保存应当确保记录和试验参与者信息的安全。 + + + +(五)【所有权转移】 必备记录所有权的转移,需符合相关法律法规的要求。 + + + +第三十条【进展报告】 主要研究者应当提供试验进展报告。 + + + +(一)主要研究者应当按照伦理审查委员会的要求提交进展报告和临床试验结果摘要。 + + + +(二)临床试验完成后,主要研究者应当及时向临床试验机构报告。 + + + +(三)主要研究者应当向申办者提供药品监督管理部门所需要的临床试验相关报告。 + + + +第三十一条【配合监查、稽查和检查】 主要研究者和临床试验机构应当接受申办者组织的监查和稽查,以及药品监督管理部门的检查,并配合提供所需的与临床试验有关的记录。 + +第四章 申办者 + + + +第三十二条【基本考虑】 申办者作为临床试验相关活动的最终责任人,应当把保护试验参与者的权益、安全和福祉以及数据的可靠性作为临床试验的基本考虑。 + + + +第三十三条【试验设计】 申办者在设计临床试验方案时,应基于足够的安全性和有效性数据支持给药途径、给药剂量和持续用药时间。方案设计应当遵循质量源于设计的原则,确保临床试验的科学性、可靠性和可操作性。 + + + +第三十四条【资源、资质和培训】 申办者团队应当具备以下条件: + + + +(一)申办者应当根据临床试验需要选用有适当资质的人员,建立临床试验的研究和管理团队,指导、监督临床试验全过程。 + + + +(二)申办者应当选用有适当资质的人员参与临床试验,包括试验的设计、实施、流程、信息和数据处理、数据核对、统计分析和撰写试验总结报告。 + + + +(三)申办者应当配备医学人员,及时解答与临床试验相关的医学问题。 + + + +(四)申办者应当建立有效的沟通渠道,确保临床试验全过程中所有参与人员能够及时沟通,并记录关键沟通内容。 + + + +第三十五条【申办者的盲态维持制度】 在盲法试验中,申办者应当建立工作制度以确保临床试验全过程保持盲态,并预防和识别破盲。 + + + +第三十六条【合同】 申办者委托服务供应商应当符合以下要求: + + + +(一)申办者可以将其临床试验的部分或者全部工作任务委托或授权给服务供应商,但应当对服务供应商进行监督和管理。受委托方如存在任务转包,应当获得申办者的书面同意。 + + + +(二)临床试验活动开始前,申办者应当与主要研究者、临床试验机构、服务供应商等所有参加临床试验的相关各方签订临床试验合同,明确各方的角色、责任、权利和义务,以及各方应当避免的、可能的利益冲突。必要时,应当更新临床试验合同。临床试验合同应当条款清晰完整,试验经费应当合理,符合市场规律。 + + + +(三)本规范中对申办者的要求,适用于承担申办者相关工作任务的服务供应商。 + + + +第三十七条【主要研究者选择】 申办者负责选择合适的主要研究者和临床试验机构,以满足开展临床试验的需要,应当制定研究者手册并及时更新,向主要研究者和伦理审查委员会提供试验方案和最新的研究者手册。 + +【中药民族药研究者手册】 中药民族药研究者手册的内容应当注明组方理论依据、筛选信息、配伍、功能、主治、已有的人用药经验、药材基原和产地等;来源于古代经典名方的中药复方制剂,注明其出处;相关药材及处方等资料。 + + + +第三十八条【实验室选择】 申办者负责选择符合相关规定且具备相应资质的实验室,承担涉及医学判断的样品检测,监督实验室对临床试验中采集标本的管理、检测、运输和储存等全流程的质量管理。禁止实施与伦理审查委员会批准的试验方案无关的生物样品检测(如基因等)。临床试验结束后,剩余标本的继续保存或者将来可能被使用等情况,应当在试验参与者签署的知情同意书中明确说明保存的时间和数据的保密性问题,以及在何种情况下数据和样本可以和其他主要研究者共享等。 + + + +第三十九条【与伦理审查委员会以及监管机构的沟通】 临床试验开始前,申办者应当向国务院药品监督管理部门提交相关的临床试验资料,并获得临床试验许可或者完成备案。递交的文件资料应当注明版本号及版本日期。申办者应当及时获取伦理审查委员会相关记录并按要求执行伦理审查意见。 + + + +第四十条【申办者监督】 申办者应当对临床试验全过程进行监督,明确工作标准和程序,确保试验过程符合试验方案和其他相关文件,遵守相关法律法规,并遵循伦理标准。申办者监督措施的范围和程度应当符合目的,与临床试验的复杂性和风险相称。 + + + +第四十一条【质量管理】 申办者应当基于风险,采用适合的体系,对临床试验全过程进行质量管理,有效设计和实施临床试验。 + + + +第四十二条【风险管理】 申办者应当对临床试验进行风险管理。 + + + +(一)【风险识别与评估】 申办者应当在试验开始前和整个试验过程中识别可能对关键质量因素产生有意义影响的风险,并对风险损害发生的可能性、可被检测到的程度、对试验参与者保护和试验结果可靠性的影响进行评估。 + + + +(二)【风险控制】 风险控制应当与风险对试验参与者权益和安全,以及试验结果可靠性影响的重要性相称。当涉及到可能影响试验参与者安全或试验结果可靠性的关键质量因素时,申办者应当预先设定风险控制可接受范围,当超出预设范围限制时,评估是否需要采取措施。 + + + +(三)【风险沟通】 申办者应当记录已识别的风险和相应的缓解措施,并与参与采取措施或受此类活动影响的人员沟通。 + + + +(四)【风险审查】 申办者应当结合临床试验期间的新知识和经验,定期审查风险控制措施,以确保现行的质量管理活动的有效性和适用性,并根据需要考虑实施额外的风险控制措施。 + + + +(五)【风险报告】 申办者应当在临床试验报告中总结并报告重要的质量问题,包括偏离预先设定可接受范围的问题和补救措施。 + + + +第四十三条【质量保证和质量控制】 申办者应当对临床试验实施质量保证和质量控制。 + + + +(一)申办者负责建立、实施和及时更新有关临床试验质量保证和质量控制相关的书面标准操作规程,确保临床试验的实施,以及数据的产生、记录、报告均遵守试验方案,并符合本规范和监管要求。 + + + +(二)【质量保证】 质量保证应当贯穿于临床试验始终,实施基于风险的策略,以识别严重不依从试验方案,以及违反本规范和监管要求的原因,从而采取纠正和预防措施。 + +【稽查】 申办者开展稽查活动应当采取与临床试验实施相关风险相称的方式。申办者的稽查独立于常规监查或质量控制职能之外,目的是评估试验管理和实施的流程是否符合试验方案、本规范和监管要求。 + + + +(三)【质量控制】 申办者应当在临床试验实施的每个阶段和环节采用基于风险的方法进行质量控制,以保证过程规范和数据可靠。在临床试验中,监查和数据管理是主要的质量控制活动。申办者应当委派合格的监查员对临床试验实施监查。 + + + +第四十四条【依从性问题】 申办者应当保证临床试验的依从性。 + + + +(一)对主要研究者、临床试验机构、申办者工作人员或服务供应商在临床试验中不遵守试验方案、标准操作规程、本规范及监管要求的情况采取适当措施予以纠正。 + + + +(二)发现对试验参与者的权益和安全,或对试验结果的可靠性产生或可能产生显著影响的不依从问题时,申办者应当及时进行根本原因分析,采取适当且充分的纠正和预防措施,及时书面报告伦理审查委员会。 + + + +(三)发现严重、持续的不依从问题时,申办者应考虑终止主要研究者、临床试验机构或服务供应商继续参加临床试验,及时书面报告伦理审查委员会,并采取措施将对试验参与者和试验结果的可靠性的影响降至最小。如违反试验方案或者本规范的问题严重时,申办者可追究相关人员的责任,并报告药品监督管理部门。 + + + +第四十五条【安全性评估和报告】 申办者应当在药物临床试验期间进行持续的安全性评估,并按照要求和时限进行报告。 + + + +(一)【可能影响安全性的问题】 申办者应当审阅并评估现有的安全性信息,将临床试验中发现的新的可能影响试验参与者安全或参与意愿、可能影响临床试验实施、可能改变伦理审查委员会批准意见的问题,及时通知试验参与者、主要研究者、临床试验机构和伦理审查委员会。 + + + +(二)【安全性评估】 申办者收到任何来源的安全性相关信息后,均应当立即分析评估,包括严重性、与试验药物的相关性以及是否为预期事件等。 + + + +(三)【SUSAR和其他潜在的严重安全性风险信息报告】 申办者应当将可疑且非预期严重不良反应、其他潜在的严重安全性风险信息快速报告国家药品监督管理局药品审评中心,快速报告的方式应当符合要求。申办者向主要研究者和伦理审查委员会递交可疑且非预期严重不良反应报告的方式应当与需采取措施的紧迫性和试验用药品安全性特征的变化相称。药品监督管理部门提出的风险处理要求、其他潜在的严重安全性风险信息和需要立即关注或采取措施的紧急安全性问题应当按照快速报告的时限要求报告伦理审查委员会和主要研究者,不得无故延迟。 + + + +(四)【DSUR报告】 申办者的药物研发期间安全性更新报告应当包括临床试验风险与获益评估,并通报给主要研究者、伦理审查委员会和国家药品监督管理局药品审评中心。 + + + +第四十六条【对试验参与者和主要研究者的保险/补偿/赔偿】 申办者应当采取适当方式保证可以给予试验参与者和主要研究者补偿或者赔偿。 + + + +(一)申办者应当提供法律上、经济上的保险或者保证用于补偿临床试验相关的损害,并与临床试验的风险性质和风险程度相适应,但不包括主要研究者和临床试验机构自身的过失所致的损害。 + + + +(二)申办者应当承担试验参与者参加临床试验相关损害的诊疗费用,以及相应的补偿。 + + + +(三)申办者和主要研究者应当及时兑付给予试验参与者的补偿或者赔偿。提供补偿的方式方法,应当符合相关法律法规。 + + + +(四)申办者应当免费向试验参与者提供试验用药品,并支付与临床试验相关的医学检测费用。 + + + +第四十七条【试验用药品】 试验用药品的制备、供应和管理应当符合以下要求: + + + +(一)【生产、标签】 申办者应当确保试验用药品在符合临床试验用药品生产质量管理相关要求的条件下生产和放行;试验用药品的标签上应当标明仅用于临床试验、临床试验信息和试验用药品信息;试验用药品在盲法试验中能够保持盲态。 + + + +(二)【贮存、运输、有效期、使用方法】 申办者应当明确规定试验用药品的贮存和运输条件、有效期、使用方法等,确保药物在运输和贮存期间不被污染或者变质,并向主要研究者和临床试验机构提供相应纸质说明,同时保留相关记录。试验用药品为疫苗的,其采购、储存、运输、接种还应当符合国家管理规定。 + + + +(三)【试验用药品运达机构】 申办者应当在临床试验获得伦理审查委员会批准和国务院药品监督管理部门许可或者备案之后,及时向主要研究者和临床试验机构提供试验用药品。 + + + +(四)【申办者试验用药品管理SOP】 申办者应当制定试验用药品的供应管理规程,包括试验用药品的接收、处理、贮存、分发、使用及回收、销毁等制度。从试验参与者处回收以及研究人员未使用的试验用药品应当返还申办者,或者采用由申办者授权的其他方式处置。所有试验用药品的管理过程应当有书面记录,全过程计数准确。 + + + +(五)【紧急揭盲机制】 在盲态试验中,应当建立紧急揭盲的规程和机制,以便在紧急医学状态必需揭盲时能够快速识别试验用药品,同时保持其他试验参与者治疗分配的盲态。 + + + +(六)【试验用药品留样】 申办者应当采取措施确保临床试验期间试验用药品的稳定性,确保仅在有效期内提供,并保存足够数量的试验用药品样品,留样数量、方式、时限等应当符合相应要求。 + + + +第四十八条【数据和记录】 申办者应当履行数据治理的责任,确保数据的可靠性、可追溯性和安全性。 + + + +(一)【数据处理和计算机化系统】 申办者应当确保临床试验中部署或使用的电子数据管理系统满足计算机化系统的要求;确认主要研究者或临床试验机构使用的计算机化系统满足临床试验的要求。 + + + +(二)【数据记录修改】 申办者不得更改主要研究者或试验参与者录入的数据,除非有正当理由,且应当在修改前获得主要研究者的同意,并进行记录。 + + + +(三)【试验数据保密性】 申办者应当使用试验参与者鉴认代码,识别每一位试验参与者的所有临床试验数据。临床试验揭盲后,申办者应当向主要研究者提供盲态试验的试验参与者的治疗信息。 + + + +(四)【统计分析】 申办者应当制定符合试验方案的统计分析计划,对统计编程以及数据处理和分析过程实施适当的质量管理并记录。 + + + +(五)【记录的保存】 申办者应当根据监管要求保存临床试验相关的必备记录,并书面告知主要研究者、临床试验机构和服务供应商对试验记录保存的要求。 + + + +第四十九条【申办者应当明确试验记录的查阅权限】 申办者应当明确试验记录的查阅权限。 + + + +(一)申办者应当在试验方案或者其他书面合同中明确主要研究者、临床试验机构和服务供应商允许申办者在监查、稽查、伦理审查及药品监督管理部门的检查中,能够直接查阅临床试验相关的源记录。 + + + +(二)申办者应当确认每位试验参与者均以书面形式同意申办者监查、稽查、伦理审查及药品监督管理部门的检查活动,能够直接查阅临床试验相关的原始医学记录。 + + + +第五十条【报告】 申办者暂停或者提前终止实施中的临床试验或临床试验实施期间申办者发生变化的,应当及时告知主要研究者和临床试验机构、伦理审查委员会和药品监督管理部门,并说明理由。 + +申办者应当按照监管要求向药品监督管理部门提交临床试验报告。临床试验报告应当全面、完整、准确反映临床试验结果,临床试验数据应当与源数据一致。 + + + +第五章 数据治理 + + + +第五十一条【数据生命周期】 申办者、主要研究者和临床试验机构应当在各自职责范围内,承担数据治理责任。数据治理贯穿临床试验数据全生命周期,确保准确报告、验证和解释临床试验相关信息。 + + + +(一)【数据采集】 申办者、主要研究者和临床试验机构在临床试验过程中将采集的数据录入计算机化系统时,应当附带相应的元数据,包括稽查轨迹。 + + + +(二)【相关元数据】 申办者、主要研究者和临床试验机构应当采用适当的方法应用、评估、访问、管理和审核元数据。 + + + +(三)【数据更正】 申办者和临床试验机构应当制定更正数据错误的相关流程,申办者和主要研究者及时更正可能影响试验结果可靠性的数据错误并确保更正过程的可追溯性。 + + + +(四)【数据传输、交换和迁移】 申办者、主要研究者和临床试验机构应当建立经验证的流程,确保在计算机化系统之间传输的电子数据(包括相关元数据)的可靠性、可追溯性和安全性,以及避免数据丢失或被篡改。 + + + +(五)【分析前数据集的最终确认】 申办者应当界定符合质量标准的中期和最终分析数据,采取及时且可靠的流程,进行数据的采集、核对、验证、审核和错误更正以及在可能的情况下修正对试验参与者的安全性和/或试验结果的可靠性造成重要影响的遗漏。统计分析前,应当按照预先制定的程序对数据集进行最终确认,数据提取和分析集的确定应当遵循统计分析计划,并予以记录。 + + + +第五十二条【数据治理中的盲态保持】 盲法试验中,临床试验各阶段都应当保持盲态完整性,并采取适当的盲态管理措施防止意外破盲导致试验偏倚。 + +在临床试验开始实施前,所有相关方应当确定访问非盲信息的角色、职责和流程,并做好记录;实施过程中,应当记录破盲或揭盲的情况,评估其对试验结果的影响,并采取相应必要的措施。 + + + +第五十三条【计算机化系统】 临床试验各方应当确保用于临床试验的计算机化系统满足对试验数据可靠性、可追溯性和安全性的要求。 + + + +(一)【计算机化系统的使用规程和培训】 应当制定计算机化系统设置、安装和使用的标准操作规程,明确使用计算机化系统时临床试验各方的职责,确保在临床试验数据采集、处理和管理过程中正确使用计算机化系统。所有使用计算机化系统的人员应当经过培训。 + + + +(二)【计算机化系统的安全管理】 计算机化系统的数据安全管理应当包含试验数据和记录的整个数据生命周期,确保对计算机化系统实施安全控制,并采取持续措施预防、检测及减少安全漏洞。 + +应当及时对计算机化系统产生的试验数据充分备份,并在系统故障时采取应急措施,防止数据发生丢失或无法访问。 + + + +(三)【计算机化系统的验证】 临床试验各方使用的计算机化系统,应当通过可靠的系统验证,符合预期用途和预先设置的技术性能,以保证试验数据的可靠性,并保证在整个试验过程中系统始终处于验证有效的状态。 + + + +(四)【技术支持】 临床试验各方应当建立工作流程,记录、评估和管理计算机化系统中出现的问题,并定期对收集的问题进行审查,识别重复或系统性问题,根据问题的严重程度进行相应地处理。 + + + +(五)【用户管理】 计算机化系统应当具备完善的用户管理、权限管理和稽查轨迹,确保只有经授权的用户方可访问和使用系统,实现访问和操作可追溯。如使用电子签名应当符合我国关于电子签名的有关要求。用户的权限应当符合其职责职能、盲法设置和用户所属组织。授权用户及其权限应当明确记录、维护和保存。 + + + +第六章 附 则 + + + +第五十四条 本规范下列用语的含义是: + +(一)主要研究者,是药物临床试验机构试验实施团队的负责人,对临床试验实施过程中试验现场试验参与者权益、安全及临床试验数据可靠性负责。 + + + +(二)其他潜在的严重安全性风险信息,指明显影响药品获益风险评估、可能改变药品用法或影响总体药品研发进程的信息。 + + + +(三)药物临床试验质量管理,对药物临床试验的质量实施管理,包括建立质量管理体系和开展具体的质量管理活动。 + + + +(四)药物临床试验质量管理体系,对临床试验全过程质量进行管理的机制,以试验参与者保护与数据可靠性为核心,明确界定并落实各方的职责与分工,能够基于风险,及时识别、预防及处理异常事件,持续推动质量改进,保障药物临床试验过程规范。 + +本规范自XXXX年X月X日起施行。 + + + + +附件2 + +《药物临床试验质量管理规范(修订稿 + +征求意见稿)》起草说明 + + + +一、起草背景 + + + +现行《药物临床试验质量管理规范》实施(以下简称2020版GCP)已有近5年时间。2020版GCP实施以来对我国药物临床试验规范化发展发挥了重要作用,有效指导了我国临床试验监管和实施实践。近年来,随着药物研发技术和理念的持续更新,国际监管规范也在发生重大调整,国际人用药品注册技术协调会(ICH)《E6(R3):药物临床试验质量管理规范技术指导原则》(即ICH GCP)自2019年启动修订,已于2025年1月定稿为ICH E6(R3)。为进一步推动我国药物临床试验高质量发展,对2020版GCP进行修订。 + + + +二、修订的主要考虑 + + + +《药物临床试验质量管理规范》(以下简称GCP)是国家药监局发布的规范性文件,指导监管部门、申请人、药物临床试验机构及其他接受委托的机构开展药物临床试验工作,是监管执法依据。修订的主要考虑如下: + + + +(一)做好与相关法律法规、其他规范性文件以及技术指导原则衔接。一是法律法规层面,与《药品管理法》《疫苗管理法》《药品管理法实施条例》《药品注册管理办法》等上位法做好对接。二是规范性文件层面,与《药物临床试验机构管理规定》《药物临床试验机构监督检查办法(试行)》、国家卫生健康委等四部门发布的《涉及人的生命科学和医学研究伦理审查办法》等规范性文件做好衔接,已有专门规范性文件规定的内容不再重复,GCP中留好接口,伦理审查部分主要保留药物临床试验相关特定要求。三是指导原则层面,重点做好与ICH E6(R3)衔接,保持基本原则和要求一致;E6(R3)中遵循当地监管要求的,进一步明确监管要求,我国相关规范性文件要求高于E6(R3)的以我国监管要求为准;E6(R3)中属于建议性做法和理念性描述,而非监管刚性要求的不纳入我国GCP,而是采取全文实施E6(R3)方式,为临床试验实施灵活性预留空间;同步启动核查、检查要点修订,我国其他技术指导原则要与GCP和E6(R3)做好衔接,根据实际需要开展制、修订工作,逐步完善我国GCP制度的技术指导原则体系,加强实施指导。 + + + +(二)充分吸收2020版GCP的经验。2020版GCP具备较好的工作基础,其篇章结构、文字表述,以及适应我国实践的特有规定,在GCP修订时予以合理保留和借鉴。 + + + +(三)注重解决我国临床试验实践中的问题。以问题为导向,针对监管部门和临床试验各参与方反映的问题,注意研究提出解决措施。 + + + +三、主要内容 + + + +《药物临床试验质量管理规范(修订稿征求意见稿)》(以下简称GCP修订稿征求意见稿)分为总则、伦理审查委员会、主要研究者和药物临床试验机构、申办者、数据治理、附则6个章节,54个条款。 + +与2020版GCP相比本次修订保留了总则、伦理审查委员会、主要研究者和药物临床试验机构、申办者、附则5个章节,增加了数据治理1个章节。考虑到全文实施E6(R3)中文版,只保留我国特有的术语及其定义,删除试验方案、研究者手册、必备文件管理3个章节。 + + + +四、需要说明的问题 + + + +(一)GCP修订稿征求意见稿与ICH E6(R3)相比重点增删的内容 + + + +1.试验参与者表述:参考E6(R3)中文版,将“受试者”改为“试验参与者”,突出试验参与者在临床试验中的主动性,体现对个体权益和主体性的尊重。 + +2.主要删减内容包括:主要研究者与药物临床试验机构章节删除知情同意书包含的细节,申办者章节删除监查和稽查的具体要求,删除试验方案、研究者手册、必备文件管理3个章节,GCP修订稿征求意见稿中保留原则性要求,具体操作参照E6(R3)中文版。 + +3.总则中保留并强调了利益冲突回避原则,增加支持新技术使用原则。伦理审查委员会章节保留2020版GCP审查中应当特别关注的情形以及伦理审查委员会应当受理并妥善处理试验参与者的相关诉求。增加伦理审查委员会文件保存以及提供记录要求。 + +4.将E6(R3)中“适用当地监管要求”进行了转化:伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定;优化安全性信息的报告流程,明确相关责任方;明确临床试验必备记录保存年限;明确生物等效性试验生物样品留样要求等。 + + + +(二)GCP修订稿征求意见稿与2020版GCP相比增删的内容 + + + +法律法规依据增加了《药品注册管理办法》。 + + + +2.增加质量源于设计、风险相称及切合目的的原则性表述,并将三个理念贯穿各章节。 + + + +3.增加新技术、新方法在临床试验应用的原则。鼓励使用新技术、新方法支持药物临床试验开展,应符合当前伦理、科学和相关法律法规要求。 + + + +4.进一步优化安全性事件报告流程及相应审查要求:主要研究者应当立即向申办者和伦理审查委员会报告严重不良事件(SAE)。可疑且非预期严重不良反应(SUSAR)和药物研发期间安全性更新报告(DSUR)由申办者报告主要研究者和伦理审查委员会,SUSAR报告的方式与需采取措施的紧迫性和临床试验用药品安全性特征的变化相称。申办者应当将SUSAR、其他潜在的严重安全性风险信息快速报告监管部门;对药品监督管理部门提出的风险处理要求、其他潜在的严重安全性风险信息、需要立即关注或采取措施的紧急安全性问题应当快速通知伦理审查委员会和主要研究者。伦理审查委员会应当重点关注并及时审查安全性事件,除2020版GCP规定的内容外,增加主要研究者报告的SAE、严重持续不依从问题、其他潜在的严重安全性风险信息;伦理审查SUSAR和DSUR的方式应遵循风险相称原则。 + + + +5.完善伦理审查委员会章节内容。明确伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定,并履行药品监督管理部门提出的监管要求。删除伦理审查委员会组成与运行具体内容,总结凝练为“组成运行要求”一款,修订审查意见类型和内容要求,与《涉及人的生命科学和医学研究伦理审查办法》保持一致。 + + + +6.进一步明确临床试验质量最终责任人为申办者,临床试验现场最终责任人为主要研究者。 + + + +7.主要研究者与药物临床试验机构章节。根据E6(R3)术语对研究者的定义,研究者是负责实施临床试验的人员(单个人),如果试验由团队实施其负责人为主要研究者(单个人),考虑到我国药物临床试验均由团队实施的实际情况,将“研究者”修改为“主要研究者”。 + + + +8.申办者章节。增加申办者监督职责、盲态保持和风险管理有关内容,以及疫苗试验用药品管理和使用要求。在本章节中将实操细节删除,按照E6(R3)中文版执行,必要时,在核查要点中体现。 + + + +9.强调质量管理。申办者采用适合的体系,对临床试验全过程进行质量管理。药物临床试验机构应当建立药物临床试验质量管理体系,并确保其有效运行。实施质量管理应当基于风险,明确了风险识别与评估、风险控制、风险沟通、风险审查和风险报告的要求及主要考虑因素。 + + + +10.增加数据治理章节。明确各方对数据治理的职责与要求。 + + + +11.附则。除本条款纳入的术语及其定义外,本规范涉及的其他术语及其定义参考ICH E6(R3)中文版术语表。纳入适应我国临床试验实践的术语,包括主要研究者、其他潜在的严重安全性风险信息、药物临床试验质量管理以及药物临床试验质量管理体系。 + + + +(三)关于药物临床试验必备文件 + + + +为指导和规范药物临床试验必备文件的保存,国家药监局2020年6月3日发布了《药物临床试验必备文件保存指导原则》。GCP修订过程中,将其与E6(R3)附件1中的《附录C:临床试验实施的必备记录》进行了对比,对比认为《药物临床试验必备文件保存指导原则》的有关要求均包含在附录C中,且附录C对必备文件进行了合理扩展。E6(R3)中文版原文实施可替代《药物临床试验必备文件保存指导原则》,GCP修订稿中将原“必备文件”表述修改为“必备记录”。 \ No newline at end of file diff --git a/ReferenceBook/GOV.UK_Risk-Adapted_Approach_说明.txt b/ReferenceBook/GOV.UK_Risk-Adapted_Approach_说明.txt new file mode 100644 index 0000000..205d53b --- /dev/null +++ b/ReferenceBook/GOV.UK_Risk-Adapted_Approach_说明.txt @@ -0,0 +1,18 @@ +GOV.UK Risk-Adapted Approach to clinical trials and Risk Assessments +来源: https://www.gov.uk/government/publications/risk-adapted-approach-to-clinical-trials-and-risk-assessments/risk-adapted-approach-to-clinical-trials-and-risk-assessments +发布日期: 2022年1月28日 + +本文件夹中已下载的 PDF: + +1. Risk-Adapted_Approach_Main_Guidance.pdf + - 主指导文件(MHRA:临床试验风险管理/风险适应方法相关) + +2. 附录示例(该页面链接的 Example 文档): + - Example_1_Type_A_Protocol_Synopsis.pdf + - Example_1_Type_A_Risk_Assessment.pdf + - Example_2_Type_A_Protocol_Synopsis.pdf + - Example_2_Type_A_Risk_Assessment.pdf + - Example_3_Type_B_Protocol_Synopsis.pdf + - Example_3_Type_B_Risk_Assessment.pdf + +说明:上述主页面正文为 HTML 形式,GOV.UK 未提供该页面的单一 PDF。若需该网页全文为 PDF,可在浏览器中打开上述链接后使用“打印 → 另存为 PDF”。 diff --git a/ReferenceBook/ICH-E6R3-Infographic-PPT-V3-Final.pptx b/ReferenceBook/ICH-E6R3-Infographic-PPT-V3-Final.pptx new file mode 100644 index 0000000..f5103c7 Binary files /dev/null and b/ReferenceBook/ICH-E6R3-Infographic-PPT-V3-Final.pptx differ diff --git a/ReferenceBook/Risk Management in clinical trials.md b/ReferenceBook/Risk Management in clinical trials.md new file mode 100644 index 0000000..0264226 --- /dev/null +++ b/ReferenceBook/Risk Management in clinical trials.md @@ -0,0 +1,97 @@ +Risk Management in Clinical Trials: Why Does It Matter? +Risk is a part of life, whether personal or professional and in the professional world of clinical trials, these risks are a strong focus of the study design. They have to be, because the entire foundation of medicine, let alone the safety of the patients and practitioners involved is at stake. + +Risks relating to quality, timeliness, safety, or budgets can affect the overall outcome of trials in several ways and it’s important when designing a study to be aware of them and to have a planned procedure for categorizing and mitigating them where possible. + +Managing risk involves identification, assessment, planning, monitoring, and reacting to threats to the safe and efficacious desired outcomes of clinical trials and is a practice that is entirely necessary to maintain a standard of care and safety to the medical and patient communities. + +As clinical trials become more complex, new risks emerge, and the essential nature of risk management is emphasized and put to the test. Every stakeholder in trials benefits from good risk management, from those interested in an ROI to those looking to further the science, and of course, those in need of urgent new treatments. + +Risk assessment, therefore, means two, intimately-related things: it is both the program and its goal. A robust risk assessment program underlies effective management of risk, and it does this by comprising a series of activities or processes that follow the entire lifecycle of the product or treatment and striving to identify and remove or avoid any factor or process that threatens its quality. + +Risk assessment achieves this by way of a risk management plan. + +Forming a Clinical Trial Risk Management Plan +Risk management needs to begin at the moment of the trial’s conception. In this way, mitigation of risk will be woven into the protocol of the study itself, and the power of risk management is maximized throughout the trial. Before we get started on how to design this plan, the relevant documentation deserves a brief mention. + +In terms of reference documents, there are two particularly useful sources of information to help with this: + +ICH Q9 – This is a guideline on quality risk management that covers areas such as hazard identification and risk ranking and filtering. +ISO 14971 – This is specifically aimed at medical devices, but contained a structured approach for effective risk management that applies to most studies as a whole. +It’s worth mentioning here, the ICH E6 R2, which covers some of the best practices of a more formal risk management process, and is discussed at the end of this article. There are also many other documents to refer to for more specific or generalized information, such as ISO 13485, which focuses on integrating active risk management in quality processes. + +The first step to forming a risk management plan is to be aware of and present with these documents. This will make it far easier to go through the following stages thoroughly. + +1 – Identify and Define the Objectives of Each Category +Now, risks need to be considered and broken down into categories based on the parts of the study they fall under. There are two ways to approach this. If you begin with the risks and work backward, you end up forming the objectives off the back of those risks. The other way is to start with objectives and allocate risks to each of them. + +For example, objectives throughout the study may fit into one of the following categories: + +Timelines – This could be meeting set patient recruitment rates or regulatory approval timelines. +Safety – Safety objectives may cover limiting the number of adverse effects or the number of patients dropping out due to these adverse effects. +Quality – The target number of protocol deviations or cases of incomplete subject diaries would be under this category. +Compliance – E.g., minimizing missing or incomplete ICF files, inadequate monitoring of investigations. +Budget – Increases in clinical trial duration or sites’ budgets could be an objective here. +This forms a foundation for the identification of risks under each category, and how these risks might affect the success of each objective. This stage could represent a section of its own, as it involves a thorough assessment and identification procedure, in which internal and external processes, people, systems, and technologies are all examined for their impact on the risk of the study. + +However, these details will be specific to each case, so a summary will suffice here. Clear identification of these risks takes time and should involve multiple different stakeholders depending on the context but the objective should be the same: find and categorize each risk that may jeopardize the ideal outcome of the objectives you have set. + +2 – Conduct a Root Cause Analysis (RCA) +The next step is a deeper assessment of the events and risks that you’ve identified, and a discussion about the tolerance limits. RCA is a critical component of risk assessment as it is a process of making everything more efficient. Starting by finding the cause of each risk, you’ll then be able to work out how to address it and whether or how critically it really needs to be addressed. + +For example, if you identify an objective to keep recruitment rates above a certain threshold, you may then find that the rarity of the disease is a potential root cause of the risk that patients may be hard to find in time, which would therefore threaten your objective. + +While this is part of the separate RCA process, doing a thorough job in the identification of objectives can include a lot of root cause identification organically. This stage is essentially about completing the thorough risk statement with the cause included, so some risks will be easier than others in this regard. + +This completes a chain between risk and objectives and helps to pad out and illuminate the specific areas that will be involved in risk mitigation and action planning sections of the later process. This is then part of the overarching goal of either minimizing or eliminating risk entirely and is described by the FDA Guidelines as the necessary step of identifying and understanding the nature, locations, and causes of risks that affect the course of the trial. + +Data is one of the core focuses of a robust risk assessment plan, as it carries with it some of the highest risks to multiple categories of objectives. Monitoring efforts, therefore, need to be focused on the most likely sources of error in the conduct of collecting and storing said data, and thresholds need to be set based on tolerance limits, as a way of setting trigger points of action, should the risk reach unacceptable levels. + +3 – Assess the Likelihood and Impact of the Risks +As an offshoot of the RCA, a deeper dive into the impact of each risk and the chances of it occurring can be performed. Broken into its likelihood and impact, each risk can then be analyzed further. Different contexts will define the scale used for this, but each can typically be ranked from 1 to 5 or 1 to 3 on a risk matrix. + +The purpose of this stage is to set yourself up to define your responses. Before you can respond to the risks, you need to know what severity rating they have in order to set priorities of action into your plan. A simple 1 to 5 scale may use impact rankings of Insignificant, Minor, Significant, Major, and Severe. These can be set against the likelihood rankings of Rare, Unlikely, Moderately Likely, Likely, and Almost Certain. + +Using this matrix, you’ll immediately see how your risks group together in different priority rankings. Risks ranking at the top of both indices will be the ones that need to most immediate and involved attention, while risks at the other end of the spectrum will be ones that can be bumped down the priority list and potentially accepted. + +When you have your rankings, it’s time to decide on what your course of action will be in response to each one. + +4 – Decide on Your Risk Responses +You can divide your responses into categories too, in order to make it easier to define the specifics of each. Consider four categories of response: + +Avoid – These are the risks that must be dealt with immediately. For example, if there can be no approval due to a design element of the trial, this is the highest impact of risk as the trial itself can’t continue and the design must be altered. +Transfer – If the team involved finds the risk outside of their control, it can be transferred to a more qualified group for decision-making in regard to its impact and the necessary responses. +Mitigate – These are risks that can be removed or reduced to a more comfortable ranking, either by reducing the likelihood or the impact. Mitigation could also involve increasing the risk detection chances. +Accept – Some risks are simply worth taking. If an objective is threatened by a risk that will pay off in the long run, and this risk can’t be mitigated in any other way, it’s classed as an acceptable risk. +For some specific examples, let’s say that a site offers significant benefits to the overall study (for example, a unique participant pool) but it’s in a location that could delay regulatory approval and the staff may not be as qualified as you would like to hit your compliance targets. + +The delays in acceptance might be considered worthwhile to reach your target population, so you consider that risk acceptable. On the other hand, compliance issues jeopardize the entire outcome of the study, so staff monitoring becomes an important focus of mitigation in terms of the risk to GCP. Assigning deeper monitoring to this part of the trial increases the risk detection rate and minimizes its impact ranking to acceptable levels. + +Risk Dynamics: The Importance of an Ongoing Assessment of Risk +While the clinical trial risk management plan should be a key focus of the early-stage trial design, it’s important to understand the dynamic nature of risks and how they change and evolve as the trial is ongoing. + +Assessment should be maintained at intervals throughout the trial conduct and to its closing phases too. Some risks have a cascade effect, so if they show up, they create other areas that need to be assessed. Others can have one root cause at one stage of the trial and another later on, meaning the responses might need to be adjusted. + +It’s important to consider risk management in clinical trials an ongoing practice and to design your management plan with this in mind. + +ICH E6 R: Risk Management in Clinical Trials – Two Best Practices to Mitigate Risk +As risk management is a diverse practice that needs to be tailored specifically to each case, there are many factors that cannot be generalized. However, there are practices worth following that relate to risk management as a whole and some areas of the process worth getting into in more detail as they pertain to clinical trials of all kinds and are of particular significance to the risk management process. + +In 2016, ICH published a document that represents a new formal standard of risk-based approaches to clinical trials. In the Quality management section of this revision, the document covers the concept of risk-based thinking and divides this into two fundamental aspects: + +Define what is critical to success – Focus on what matters. In the development stages of the trial protocol, and at the “Identify and Define” stage of the management plan, it’s important to clearly identify specific data and processes that are critical to human protection and the reliability of the results of the trial. This is a matter of how to think in a way that prioritizes the desired outcome of risk management, rather than the process. Your risk management plan is about the return on the significant investment into clinical trials from every stakeholder, whether it’s money, time, or therapeutic treatment. A study’s risk management helps bring these returns to life, resulting in a safe, effective, and powerful set of data that covers the needs of everyone involved. +Manage the critical elements of a clinical trial – This relates to the ongoing and dynamic nature of the risks involved in clinical trials. During the whole lifecycle of the study, it’s important to maintain an effort of identifying, evaluating, controlling, communicating, reviewing, and reporting on risks. +Having everyone involved on the same page is a key component to successful risk management. Breaking down the thinking around risk into these two categories opens up the way for all stakeholders to take part in the following suggested best practices: + +Provide adequate support to clinical trial teams – Consider assigning a Risk Manager as a champion of the risk management process. They can be responsible for guiding thought processes and controlling appropriate documentation. +Start early – Since risk presents itself at every stage of the trial, including protocol design, risk management should precede it. +Balance critical thinking and available resources – It’s important to use the available resources to help identify risk, but beware of relying on them too rigidly. Risk managers should be able to navigate these documents while maintaining critical thinking in the RM team. +Promote cross-organizational risk identification – Include as many perspectives as possible, including even the sponsors and vendors, where appropriate. +Set appropriate thresholds – Setting between 3 and 5 predefined quality tolerance limits is key to keeping things from getting over-complicated and to illuminate systemic, protocol-level issues that can impact outcomes. +Integrate multiple data sources when rereviewing risk registers to drive the right downstream decisions – And perform risk assessment at regular intervals to assess if mitigations are effective and whether new risks are emerging. +Communicate regularly internally and externally – Ensure real-time access to the risk register is available to reduce latency and improve communications access to the most current information. +Apply lessons learned – Adapt in response to root cause analyses in the form of lessons-learned activities. +Conclusion +Risk assessment is a systematic process of identifying, analyzing, and responding to events or processes that jeopardize a trial’s objectives. Risks come in many shapes and forms, and from all directions, and it is in the effective management of these risks that trial design and execution is able to run with the best possible outcomes for all stakeholders. + +Start early, and break the process into stages of identification, root cause analysis, likelihood and impact evaluation, and tolerance thresholds. From there, follow some best practices and maintain critical thinking while applying these principles throughout the study. \ No newline at end of file diff --git a/ReferenceBook/Risk Assessment.pdf b/ReferenceBook/Risk Assessment.pdf new file mode 100644 index 0000000..823403d Binary files /dev/null and b/ReferenceBook/Risk Assessment.pdf differ diff --git a/ReferenceBook/Risk Management in Clinical Trials Assessment of Current Practices at Portuguese Clinical Trial Sites.pdf b/ReferenceBook/Risk Management in Clinical Trials Assessment of Current Practices at Portuguese Clinical Trial Sites.pdf new file mode 100644 index 0000000..5ec118c Binary files /dev/null and b/ReferenceBook/Risk Management in Clinical Trials Assessment of Current Practices at Portuguese Clinical Trial Sites.pdf differ diff --git a/ReferenceBook/Risk-Adapted_Approach_Main_Guidance.pdf b/ReferenceBook/Risk-Adapted_Approach_Main_Guidance.pdf new file mode 100644 index 0000000..a33c29d Binary files /dev/null and b/ReferenceBook/Risk-Adapted_Approach_Main_Guidance.pdf differ diff --git a/ReferenceBook/Risk-Proportionality-Framework_Final_V1.0.pptx b/ReferenceBook/Risk-Proportionality-Framework_Final_V1.0.pptx new file mode 100644 index 0000000..b71cc03 Binary files /dev/null and b/ReferenceBook/Risk-Proportionality-Framework_Final_V1.0.pptx differ diff --git a/ReferenceBook/Risk_Assessment_for_trial_SOP.docx b/ReferenceBook/Risk_Assessment_for_trial_SOP.docx new file mode 100644 index 0000000..d8a65ae Binary files /dev/null and b/ReferenceBook/Risk_Assessment_for_trial_SOP.docx differ diff --git a/ReferenceBook/医药费用理算清单.csv b/ReferenceBook/医药费用理算清单.csv new file mode 100644 index 0000000..8c09d34 --- /dev/null +++ b/ReferenceBook/医药费用理算清单.csv @@ -0,0 +1,16 @@ +费用发票底稿(医疗、护理、营养、交通等),,,,,,,,预期费用,,, +支出项目,日期,金额,医保金额,自费金额,不予支持金额,,,项目,原由,金额,预估逻辑 +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +,,,,,,,,,,, +总计,,,,,,,,,,, diff --git a/ReferenceBook/多种保险方案.docx b/ReferenceBook/多种保险方案.docx new file mode 100644 index 0000000..e83d605 Binary files /dev/null and b/ReferenceBook/多种保险方案.docx differ diff --git a/ReferenceBook/理赔评估.csv b/ReferenceBook/理赔评估.csv new file mode 100644 index 0000000..ae779d9 --- /dev/null +++ b/ReferenceBook/理赔评估.csv @@ -0,0 +1,45 @@ +内部案件编号,,,,,,,,,,,, +适用说明,所有理赔申请,在获得基本信息后,均因有此表格进行评估。对于SUSAR应完成表格中的每一项目;对于其他理赔申请,也应参考该表格,以便获得足够的依据,遵循“公正”与责任对等进行后续的协商、谈判。,,,,,,,,,,, +【步骤A.1】材料完整性评估,在本报告定稿时,以下内容必须均有勾选。收集完成包括,并不需要收集。当考虑可能为SUSAR时,按此表格评估。如明确非SUSAR,则跳过此表格评估。,,,,,,,,,,,, +A.1 材料完整性:用于评估的材料,,,,,,,,,,,, +投保时试验方案,收集完成,最新版本试验方案,,,,,,,,,, +投保时研究者手册,收集完成,最新版研究者手册,,,,,,,,,, +已签署的ICF,,被保险人与试验机构协议,,,,,,,,,, +PI签署过的SAE表,,申办者评估后SUSAR,,,,,,,,,, +递交监管的XML,,ACK文件,,,,,,,,,, +AE/SAE治疗过程,,AE/SAE治疗费用,,,,,,,,,, +【步骤A.2】对比投保时的方案、IB与受试者入组时的方案、IB,关注两者的差异。是否存在变更,变更的原因。判断依据记录与A.2底稿,以便质控。,,,,,,,,,,,, +A.2 方案与IB变更情况:是否与投保时方案发生重大变化,,,无重大变化,,,,,,,,, +入组标准,一般变更,排除标准,一般变更,,,,,,,,, +用药计划变化,一般变更,项目执行状态,一般变更,,,,,,,,, +安全性列表,无变更,,,,,,,,,,, +结论,"当前的试验方案与购买保险时的方案无重大变化, +现行保单有效。",,,,,,,,,,, +【步骤A.3】受试者编号、入组情况,与保单覆盖的受试者数量是否匹配。避免保单覆盖10位患者,但受试者已为第15位入组者,超出保障范围。判断依据记录与A.3底稿,以便质控。,,,,,,,,,,,, +A.3 受试者是否在本保单范围,,,是,,,,,,,,, +本受试者编号,,已入组受试者数量,,,,,,,,,, +结论,,,,,,,,,,,, +【步骤A.4】通过受试者入组标准、入组后的治疗过程,判断是否存在严重的方案违背;如出现严重的方案违背,可能导致该伤害不受保险条款保障。判断依据记录与A.4底稿,以便质控。,,,,,,,,,,,, +A.4 判断是否存在严重的方案违背,,,轻微方案违背,,,,,,,,, +入组标准,,排除标准,,,,,,,,,, +用药记录与方案,,,,,,,,,,,, +结论,"该理赔申请中的受试者治疗过程轻微方案违背, +继续评估事件性质。",,,,,,,,,,, +【步骤A.5】检查并确认是否为SUSAR报告,研究者与申办者的判断一致时,直接采纳。如不一致,则审阅其判断原由。采信明确有原由表述的、原由有资料支撑的、从理论与专业性角度更合理的。完全无法评估时,组织3位与试验无关的药物安全专家进行独立评估。,,,,,,,,,,,, +A.5 检查是否为SUSAR,,,,,,,,,,,, +,研究者,申办者,独立评估,,,,,,,,, +是否预期,否,否,,,,,,,,,, +是否相关,,,,,,,,,,,, +是否严重,,,,,,,,,,,, +递交监管及时性,,是否受试药物,,,,,,,,,, +结论,,,,,,,,,,,, +【步骤A.6】对相关性进行进一步研究,判断相关程度。研究者判断、同类产品数据(包括国外已上市)、发表的文章,对安全性进行快速检索与评估。与研究者评估与相关资料存在明显差异,或无资料可支持,必要时则可安排独立评估。,,,,,,,,,,,, +A.6 事件原因研究,,,,,,,,,,,, +,研究者,同类产品/文献/机理,独立评估,,,,,,,,, +与受试药物的相关性,,,,,,,,,,,, +与试验流程的相关性,,,,,,,,,,,, +结论,,,,,,,,,,,, +【步骤A.7】对上述评估进行总结,做出是否按SUSAR理赔的建议。无论SUSAR或非SUSAR,决定需进行赔偿时,则继续L部分(法律评估)。,,,,,,,,,,,, +A.7 综合结论,,,按主险赔付,,,,,,,,, +,,,,,,,,,,,, +下接【步骤L.1】试算赔偿、补偿金额,与申请人(受试者)沟通方案设计与执行。,,,,,,,,,,,, diff --git a/ReferenceBook/理赔评估必要信息收集.csv b/ReferenceBook/理赔评估必要信息收集.csv new file mode 100644 index 0000000..9a26bcc --- /dev/null +++ b/ReferenceBook/理赔评估必要信息收集.csv @@ -0,0 +1,60 @@ +内部案件编号,,,,,,,,,,,,,, +,,,,,,,,,,,,,, +,临床试验,责任保险事件信息登记,,,,,,,,,,,, +"【步骤C.1】填表说明:与保司沟通时填写,用于逐步完成信息收集。 +接到理赔案件当日时完成;获得准许联系报案人或报案人指定的联系人。",,,,,,,,,,,,,, +保单与理赔申请人【从保司处获得】,,,,,,,,,,,,,, +承保人,,保司案件号,,,,,,,,,,,, +保单号,,投保人,,,,,,,,,,,, +保险期限,,投保人同意申请理赔,是,,,,,,,,,,, +报案来源,,报案人姓名,,,,,,,,,,,, +报案人联系方式,,,,,,,,,,,,,, +信息提供人,,,,,,,,,,,,,, +医院端岗位,(如CRC),姓名,CRC张,,,,,,,,,,, +联系电话,,邮箱,,,,,,,,,,,, +申办者端岗位,(如PV团队),姓名,药物警戒专员,,,,,,,,,,, +联系电话,,邮箱,,,,,,,,,,,, +【步骤C.2】与指定的医院端信息提供人联系,获得以下信息,并在获得信息后,要求对方邮件发送理赔必须的文件。,,,,,,,,,,,,,, +项目信息,,,,,,,,,,,,,, +临床试验方案全称,项目123,方案编号/版本号,,,,,,,,,,,, +产品名称,,产品类型,,,,,,,,,,,, +研发阶段,,,,,,,,,,,,,, +研究中心名称,,,,,,,,,,,,,, +中心编号,,受试者编号,1010,,,,,,,,,,, +受试者姓名缩写,,年龄/性别,,,,,,,,,,,, +入组日期,,退出日期,,,,,,,,,,,, +受试者当前状态,,曾签署几次ICF,,,,,,,,,,,, +事件信息与研究者评价,,,,,,,,,,,,,, +研究者获知事件时间,,上报申办者时间,,,,,,,,,,,, +是否报告伦理,,报告伦理时间,,,,,,,,,,,, +事件名称,"2025.10.24 骨髓抑制 CTCAE 3 +2025.12.3 骨髓抑制 CTCAE 3",事件结局,,太和医院:可能相关以上/100%,住院,,,,,,,,, +是否方案偏离/违背,,研究者对相关性评价,8008:肯定有关,,,,,,,,,,, +"收集原始文件的邮件模板: +CRC张 +您好,关于项目123项目中的受试者1010的保险理赔申请已收悉。请按理赔要求提供以下信息: +-最新版本的临床试验方案; +-该中心的临床试验合同; +-伦理批件; +-该患者签署的知情同意书; +-研究者签署过的AE/SAE报告; +- 受试者发生事件后的治疗记录 +-治疗费用票据 +请尽快提供相关资料,如无法一次性提供,也可分多次发送。",,,,"湖北十堰; +费用:4万7",,,,,,,,,, +【步骤C.3】与指定的企业端信息提供人联系,获得以下信息,并在获得信息后,要求对方邮件发送理赔必须的文件。,,,,,,,,,,,,,, +事件信息与申办方判断,,,,,,,,,,,,,, +事件名称,,申办者相关性评价,,,,,,,,,,,, +预期性,,严重性,,,,,,,,,,,, +是否报告监管,,报告监管时间,,,,,,,,,,,, +是否揭盲,,揭盲结果,,,,,,,,,,,, +"收集原始文件的邮件模板: +药物警戒专员 +您好,关于项目123项目中的受试者1010的保险理赔申请已收悉。请按理赔要求提供以下信息: +-最新版本的研究者手册; +-或,不良反应列表; +-SUSAR报告PDF版本; +-递交监管的XML文件(含盲底信息); +-ACK文件; +-AOSE报告",,,,,,,,,,,,,, +下接信息评估【步骤A.X】,,,,,,,,,,,,,, diff --git a/ReferenceBook/理赔评估结论建议.csv b/ReferenceBook/理赔评估结论建议.csv new file mode 100644 index 0000000..100d75e --- /dev/null +++ b/ReferenceBook/理赔评估结论建议.csv @@ -0,0 +1,7 @@ +方案与IB变更情况,,,, +,入组时,受试者入组时,差异点,评判 +入选标准,,,, +排除标准,,,, +治疗组别,,,, +用药方案,,,, +安全性信息,,,, diff --git a/convert_to_word.py b/convert_to_word.py new file mode 100644 index 0000000..20a3ac8 --- /dev/null +++ b/convert_to_word.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +将Markdown文件转换为Word文档 +""" + +import re +from docx import Document +from docx.shared import Pt, RGBColor, Inches +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml.ns import qn + +def parse_markdown_to_word(md_file, docx_file): + """将Markdown文件转换为Word文档""" + + # 读取Markdown文件 + with open(md_file, 'r', encoding='utf-8') as f: + content = f.read() + + # 创建Word文档 + doc = Document() + + # 设置中文字体 + def set_chinese_font(run): + run.font.name = '宋体' + run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') + + # 设置标题字体 + def set_title_font(run, size=16, bold=True): + run.font.size = Pt(size) + run.font.bold = bold + run.font.color.rgb = RGBColor(0, 0, 0) + set_chinese_font(run) + + # 设置正文字体 + def set_normal_font(run, size=12): + run.font.size = Pt(size) + run.font.bold = False + set_chinese_font(run) + + # 分割内容为行 + lines = content.split('\n') + + i = 0 + while i < len(lines): + line = lines[i].strip() + + if not line: + # 空行 + doc.add_paragraph() + i += 1 + continue + + # 一级标题 + if line.startswith('# '): + p = doc.add_heading(line[2:], level=1) + for run in p.runs: + set_title_font(run, 20) + i += 1 + continue + + # 二级标题 + if line.startswith('## '): + p = doc.add_heading(line[3:], level=2) + for run in p.runs: + set_title_font(run, 18) + i += 1 + continue + + # 三级标题 + if line.startswith('### '): + p = doc.add_heading(line[4:], level=3) + for run in p.runs: + set_title_font(run, 16) + i += 1 + continue + + # 四级标题 + if line.startswith('#### '): + p = doc.add_heading(line[5:], level=4) + for run in p.runs: + set_title_font(run, 14) + i += 1 + continue + + # 代码块(跳过,因为Word中代码块处理较复杂) + if line.startswith('```'): + i += 1 + while i < len(lines) and not lines[i].strip().startswith('```'): + i += 1 + i += 1 + continue + + # 列表项 + if line.startswith('- ') or line.startswith('* '): + text = line[2:].strip() + # 处理粗体 + text = re.sub(r'\*\*(.+?)\*\*', r'\1', text) + p = doc.add_paragraph(text, style='List Bullet') + for run in p.runs: + set_normal_font(run) + i += 1 + continue + + # 有序列表 + if re.match(r'^\d+\.\s', line): + text = re.sub(r'^\d+\.\s', '', line) + # 处理粗体 + text = re.sub(r'\*\*(.+?)\*\*', r'\1', text) + p = doc.add_paragraph(text, style='List Number') + for run in p.runs: + set_normal_font(run) + i += 1 + continue + + # 普通段落 + # 处理粗体、斜体等格式 + text = line + p = doc.add_paragraph() + + # 分割文本,处理格式标记 + parts = re.split(r'(\*\*.*?\*\*|`.*?`)', text) + + for part in parts: + if not part: + continue + + if part.startswith('**') and part.endswith('**'): + # 粗体 + run = p.add_run(part[2:-2]) + set_normal_font(run) + run.bold = True + elif part.startswith('`') and part.endswith('`'): + # 代码 + run = p.add_run(part[1:-1]) + set_normal_font(run) + run.font.name = 'Courier New' + else: + # 普通文本 + run = p.add_run(part) + set_normal_font(run) + + i += 1 + + # 保存文档 + doc.save(docx_file) + print(f"成功将 {md_file} 转换为 {docx_file}") + +if __name__ == '__main__': + import sys + md_file = 'RMO网站需求文档.md' + docx_file = 'RMO网站需求文档.docx' + + try: + parse_markdown_to_word(md_file, docx_file) + print(f"\n转换完成!文件已保存为: {docx_file}") + except ImportError: + print("错误: 需要安装 python-docx 库") + print("请运行: pip install python-docx") + sys.exit(1) + except Exception as e: + print(f"转换过程中出现错误: {e}") + sys.exit(1) + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..b70dac9 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + RMO一站式临床试验风险管理 + + +
+ + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ca68fb1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3524 @@ +{ + "name": "rmo-website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rmo-website", + "version": "1.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c5c2d10 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "rmo-website", + "version": "1.0.0", + "description": "RMO一站式临床试验风险管理网站", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} + diff --git a/pic/PV Model.png b/pic/PV Model.png new file mode 100644 index 0000000..be06059 Binary files /dev/null and b/pic/PV Model.png differ diff --git a/pic/Risk Management Chart.png b/pic/Risk Management Chart.png new file mode 100644 index 0000000..4cd92dc Binary files /dev/null and b/pic/Risk Management Chart.png differ diff --git a/pic/logo/vDano.jpg b/pic/logo/vDano.jpg new file mode 100644 index 0000000..28822cc Binary files /dev/null and b/pic/logo/vDano.jpg differ diff --git a/pic/logo/中再logo.png b/pic/logo/中再logo.png new file mode 100644 index 0000000..ad8b496 Binary files /dev/null and b/pic/logo/中再logo.png differ diff --git a/pic/logo/中国大地保险logo.jpg b/pic/logo/中国大地保险logo.jpg new file mode 100644 index 0000000..fe2e970 Binary files /dev/null and b/pic/logo/中国大地保险logo.jpg differ diff --git a/pic/logo/亚太财产保险logo.webp b/pic/logo/亚太财产保险logo.webp new file mode 100644 index 0000000..16e5af4 Binary files /dev/null and b/pic/logo/亚太财产保险logo.webp differ diff --git a/pic/logo/华泰财产.jpg b/pic/logo/华泰财产.jpg new file mode 100644 index 0000000..c8f8350 Binary files /dev/null and b/pic/logo/华泰财产.jpg differ diff --git a/pic/logo/太平洋保险logo.jpeg b/pic/logo/太平洋保险logo.jpeg new file mode 100644 index 0000000..e7b363e Binary files /dev/null and b/pic/logo/太平洋保险logo.jpeg differ diff --git a/pic/logo/平安logo.png b/pic/logo/平安logo.png new file mode 100644 index 0000000..fb28af1 Binary files /dev/null and b/pic/logo/平安logo.png differ diff --git a/pic/logo/药盾logo.webp b/pic/logo/药盾logo.webp new file mode 100644 index 0000000..3f5e605 Binary files /dev/null and b/pic/logo/药盾logo.webp differ diff --git a/pic/why risk management.png b/pic/why risk management.png new file mode 100644 index 0000000..c6f3c32 Binary files /dev/null and b/pic/why risk management.png differ diff --git a/pic/上传方案截图.png b/pic/上传方案截图.png new file mode 100644 index 0000000..1a28bed Binary files /dev/null and b/pic/上传方案截图.png differ diff --git a/pic/保证基金的基本逻辑.png b/pic/保证基金的基本逻辑.png new file mode 100644 index 0000000..52382df Binary files /dev/null and b/pic/保证基金的基本逻辑.png differ diff --git a/pic/申办者和持有人责任风险管理20251021_16.png b/pic/申办者和持有人责任风险管理20251021_16.png new file mode 100644 index 0000000..5d86742 Binary files /dev/null and b/pic/申办者和持有人责任风险管理20251021_16.png differ diff --git a/pic/申办者和持有人责任风险管理20251021_17.png b/pic/申办者和持有人责任风险管理20251021_17.png new file mode 100644 index 0000000..5f02abd Binary files /dev/null and b/pic/申办者和持有人责任风险管理20251021_17.png differ diff --git a/pic/申办者和持有人责任风险管理20251021_18.png b/pic/申办者和持有人责任风险管理20251021_18.png new file mode 100644 index 0000000..7ae873f Binary files /dev/null and b/pic/申办者和持有人责任风险管理20251021_18.png differ diff --git a/pic/申办者和持有人责任风险管理20251021_29.png b/pic/申办者和持有人责任风险管理20251021_29.png new file mode 100644 index 0000000..a402f68 Binary files /dev/null and b/pic/申办者和持有人责任风险管理20251021_29.png differ diff --git a/pic/申办者和持有人责任风险管理20251021_30.png b/pic/申办者和持有人责任风险管理20251021_30.png new file mode 100644 index 0000000..0fa9eed Binary files /dev/null and b/pic/申办者和持有人责任风险管理20251021_30.png differ diff --git a/public/pic/PV Model.png b/public/pic/PV Model.png new file mode 100644 index 0000000..be06059 Binary files /dev/null and b/public/pic/PV Model.png differ diff --git a/public/pic/Risk Management Chart.png b/public/pic/Risk Management Chart.png new file mode 100644 index 0000000..4cd92dc Binary files /dev/null and b/public/pic/Risk Management Chart.png differ diff --git a/public/pic/logo/vDano.jpg b/public/pic/logo/vDano.jpg new file mode 100644 index 0000000..28822cc Binary files /dev/null and b/public/pic/logo/vDano.jpg differ diff --git a/public/pic/logo/中再logo.png b/public/pic/logo/中再logo.png new file mode 100644 index 0000000..ad8b496 Binary files /dev/null and b/public/pic/logo/中再logo.png differ diff --git a/public/pic/logo/中国大地保险logo.jpg b/public/pic/logo/中国大地保险logo.jpg new file mode 100644 index 0000000..fe2e970 Binary files /dev/null and b/public/pic/logo/中国大地保险logo.jpg differ diff --git a/public/pic/logo/亚太财产保险logo.webp b/public/pic/logo/亚太财产保险logo.webp new file mode 100644 index 0000000..16e5af4 Binary files /dev/null and b/public/pic/logo/亚太财产保险logo.webp differ diff --git a/public/pic/logo/华泰财产.jpg b/public/pic/logo/华泰财产.jpg new file mode 100644 index 0000000..c8f8350 Binary files /dev/null and b/public/pic/logo/华泰财产.jpg differ diff --git a/public/pic/logo/太平洋保险logo.jpeg b/public/pic/logo/太平洋保险logo.jpeg new file mode 100644 index 0000000..e7b363e Binary files /dev/null and b/public/pic/logo/太平洋保险logo.jpeg differ diff --git a/public/pic/logo/平安logo.png b/public/pic/logo/平安logo.png new file mode 100644 index 0000000..fb28af1 Binary files /dev/null and b/public/pic/logo/平安logo.png differ diff --git a/public/pic/logo/药盾logo.webp b/public/pic/logo/药盾logo.webp new file mode 100644 index 0000000..3f5e605 Binary files /dev/null and b/public/pic/logo/药盾logo.webp differ diff --git a/public/pic/why risk management.png b/public/pic/why risk management.png new file mode 100644 index 0000000..c6f3c32 Binary files /dev/null and b/public/pic/why risk management.png differ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..4e2e23c --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,125 @@ +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' +import Layout from './components/Layout' +import ProtectedRoute from './components/ProtectedRoute' +import DashboardLayout from './components/DashboardLayout' +import { QuoteModalProvider } from './contexts/QuoteModalContext' +import QuoteRequestModal from './components/QuoteRequestModal' +import Home from './pages/Home' +import RmoMode from './pages/RmoMode' +import ResourceCenter from './pages/ResourceCenter' +import RiskDutiesOverview from './pages/RiskDutiesOverview' +import PostMarket from './pages/PostMarket' +import Overseas from './pages/Overseas' +import FAQ from './pages/FAQ' +import Login from './pages/Login' +import Sponsor from './pages/Sponsor' +import Institution from './pages/Institution' +import Participant from './pages/Participant' +import ServiceProvider from './pages/ServiceProvider' +import Holder from './pages/Holder' +import SmartAcquisition from './pages/SmartAcquisition' +import PVReport from './pages/PVReport' +import DrugSafetyDict from './pages/DrugSafetyDict' +// 登录后系统页面 +import Dashboard from './pages/dashboard/Dashboard' +import ProjectList from './pages/dashboard/ProjectList' +import ProjectDetail from './pages/dashboard/ProjectDetail' +import InquiryList from './pages/dashboard/InquiryList' +import InquiryDetail from './pages/dashboard/InquiryDetail' +import ClaimProgress from './pages/dashboard/ClaimProgress' +import ClaimDetail from './pages/dashboard/ClaimDetail' +import Tools from './pages/dashboard/Tools' +import PremiumCalculator from './pages/dashboard/PremiumCalculator' +import ProjectQuotes from './pages/dashboard/ProjectQuotes' +import ICFEditor from './pages/dashboard/ICFEditor' +import RiskScoring from './pages/dashboard/RiskScoring' +import ProtocolRiskAssessment from './pages/dashboard/ProtocolRiskAssessment' +import DrugSafetyQuery from './pages/dashboard/DrugSafetyQuery' + +function App() { + return ( + + + + {/* 免登录浏览区路由 */} + + + } /> + + {/* 风险职责(原各方关注)路由 */} + } /> + } /> + } /> + } /> + } /> + + {/* 风险数据路由 */} + } /> + } /> + } /> + + {/* 风险活动:临床试验(原RMO模式)路由 */} + } /> + } /> + } /> + } /> + + {/* 风险活动:上市应用路由 */} + } /> + + {/* 海外风险路由 */} + } /> + + {/* 资源中心(原体系管理)路由,含常见问题 */} + } /> + } /> + } /> + } /> + } /> + + {/* 常见问题保留原路径以兼容 */} + } /> + + {/* 其他路由 */} + } /> + } /> + + + } /> + + {/* 登录后系统路由(需权限验证) */} + + + + } /> + {/* 投保人可见 */} + } /> + } /> + } /> + {/* 保险人可见 */} + } /> + } /> + {/* 投保人、保险人可见 */} + } /> + } /> + {/* 投保人可见 */} + } /> + } /> + } /> + } /> + } /> + } /> + + + + } /> + + + + + ) +} + +export default App diff --git a/src/components/DashboardLayout.css b/src/components/DashboardLayout.css new file mode 100644 index 0000000..628fe4e --- /dev/null +++ b/src/components/DashboardLayout.css @@ -0,0 +1,398 @@ +.dashboard-layout { + display: flex; + min-height: 100vh; + background: var(--bg-color, #f5f5f5); +} + +/* ========== 侧边栏 ========== */ +.dashboard-sidebar { + width: 125px; + background: #fff; + border-right: 1px solid var(--border-color, #e8e8e8); + display: flex; + flex-direction: column; + transition: width 0.3s ease; + position: fixed; + height: 100vh; + left: 0; + top: 0; + z-index: 100; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05); +} + +.dashboard-sidebar.closed { + width: 60px; +} + +.sidebar-header { + padding: var(--padding-md, 16px) var(--padding-sm, 12px); + border-bottom: 1px solid var(--border-color, #e8e8e8); + display: flex; + justify-content: space-between; + align-items: center; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + text-decoration: none; + color: var(--brand-primary, #0ea5e9); + align-items: center; + text-align: center; +} + +.sidebar-logo h2 { + margin: 0; + font-size: 18px; + font-weight: bold; + line-height: 1.2; +} + +.sidebar-logo span { + font-size: 10px; + color: var(--text-light, #666); + line-height: 1.2; + margin-top: 2px; +} + +.dashboard-sidebar.closed .sidebar-logo span { + display: none; +} + +.dashboard-sidebar.closed .sidebar-logo h2 { + font-size: 16px; +} + +.sidebar-toggle { + background: none; + border: none; + cursor: pointer; + font-size: 14px; + color: var(--text-light, #666); + padding: 4px; + flex-shrink: 0; +} + +.dashboard-sidebar.closed .sidebar-toggle { + margin: 0 auto; +} + +.sidebar-nav { + flex: 1; + padding: var(--padding-sm, 12px) 0; + overflow-y: auto; +} + +.nav-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--padding-sm, 12px) var(--padding-xs, 8px); + color: var(--text-color, #333); + text-decoration: none; + transition: all 0.2s; + border-left: 3px solid transparent; + text-align: center; + min-height: 70px; +} + +.nav-item:hover { + background: rgba(14, 165, 233, 0.05); + color: var(--brand-primary, #0ea5e9); +} + +.nav-item.active { + background: rgba(14, 165, 233, 0.1); + color: var(--brand-primary, #0ea5e9); + border-left-color: var(--brand-primary, #0ea5e9); + font-weight: 500; +} + + +.nav-icon { + font-size: 16px; + margin-bottom: var(--padding-xs, 8px); + width: auto; + text-align: center; + display: block; +} + +.nav-text { + font-size: 12px; + line-height: 1.2; + word-break: break-all; + text-align: center; +} + +.dashboard-sidebar.closed .nav-text { + display: none; +} + +.dashboard-sidebar.closed .nav-icon { + margin-bottom: 0; +} + +.dashboard-sidebar.closed .nav-item { + flex-direction: row; + justify-content: center; + min-height: auto; + padding: var(--padding-md, 16px); + border-left: 3px solid transparent; + border-top: none; +} + +.dashboard-sidebar.closed .nav-item.active { + border-left-color: var(--brand-primary, #0ea5e9); + border-top: none; +} + +.nav-group { + margin-top: var(--padding-sm, 12px); +} + +.nav-submenu { + padding-left: var(--padding-xs, 8px); + background: rgba(14, 165, 233, 0.02); + margin-top: var(--padding-xs, 8px); +} + +.nav-subitem { + display: block; + padding: var(--padding-xs, 8px) var(--padding-sm, 12px); + color: var(--text-light, #666); + text-decoration: none; + font-size: 11px; + transition: all 0.2s; + text-align: center; + line-height: 1.4; +} + +.nav-subitem:hover { + color: var(--brand-primary, #0ea5e9); + background: rgba(14, 165, 233, 0.05); +} + +.nav-subitem.active { + color: var(--brand-primary, #0ea5e9); + font-weight: 500; +} + +/* ========== 主内容区 ========== */ +.dashboard-main { + flex: 1; + margin-left: 125px; + display: flex; + flex-direction: column; + transition: margin-left 0.3s ease; +} + +.dashboard-sidebar.closed ~ .dashboard-main { + margin-left: 60px; +} + +.dashboard-header { + background: #fff; + padding: var(--padding-md, 16px) var(--padding-xl, 24px); + border-bottom: 1px solid var(--border-color, #e8e8e8); + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.header-left { + display: flex; + align-items: center; + gap: var(--padding-xl, 24px); + flex: 1; +} + +.header-logo { + display: flex; + flex-direction: column; + text-decoration: none; + color: var(--brand-primary, #0ea5e9); + margin-right: var(--padding-lg, 20px); +} + +.header-logo h2 { + margin: 0; + font-size: 24px; + font-weight: 700; + line-height: 1.2; +} + +.header-logo span { + font-size: 12px; + color: var(--text-light, #666); + line-height: 1.2; +} + +.header-nav { + display: flex; + gap: var(--padding-lg, 20px); + align-items: center; +} + +.header-nav a { + color: var(--text-color, #333); + font-size: 15px; + padding: 8px 0; + position: relative; + text-decoration: none; + transition: color 0.3s ease; +} + +.header-nav a:hover { + color: var(--brand-primary, #0ea5e9); +} + +.header-nav a.active { + color: var(--brand-primary, #0ea5e9); + font-weight: 600; +} + +.header-nav a.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background: var(--brand-primary, #0ea5e9); +} + +/* 下拉菜单样式 */ +.nav-dropdown { + position: relative; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + background: var(--white, #fff); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 8px; + min-width: 180px; + padding: 10px 0; + margin-top: 10px; + z-index: 1000; + border: 1px solid var(--border-color, #e8e8e8); +} + +.dropdown-menu a { + display: block; + padding: 10px 20px; + color: var(--text-color, #333); + font-size: 14px; + text-decoration: none; + transition: background 0.3s ease, color 0.3s ease; +} + +.dropdown-menu a:hover { + background: var(--bg-color, #f5f5f5); + color: var(--brand-primary, #0ea5e9); +} + +.dropdown-menu a.active { + background: var(--bg-color, #f5f5f5); + color: var(--brand-primary, #0ea5e9); + font-weight: 600; +} + +.dropdown-menu a.active::after { + display: none; +} + +.header-right { + display: flex; + align-items: center; + gap: var(--padding-lg, 20px); +} + +.user-info { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.user-name { + font-weight: 500; + color: var(--text-color, #333); + font-size: 14px; +} + +.user-role { + font-size: 12px; + color: var(--text-light, #666); +} + +.logout-btn { + background: var(--brand-primary, #0ea5e9); + color: #fff; + border: none; + padding: var(--padding-sm, 12px) var(--padding-lg, 20px); + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; +} + +.logout-btn:hover { + background: var(--brand-primary-dark, #0284c7); +} + +.dashboard-content { + flex: 1; + padding: var(--padding-xl, 24px); + overflow-y: auto; +} + +/* 响应式设计 */ +@media (max-width: 1200px) { + .header-nav { + gap: var(--padding-md, 16px); + } + + .header-nav a { + font-size: 14px; + } +} + +@media (max-width: 768px) { + .dashboard-sidebar { + transform: translateX(-100%); + } + + .dashboard-sidebar.open { + transform: translateX(0); + } + + .dashboard-main { + margin-left: 0; + } + + .header-left { + flex-direction: column; + align-items: flex-start; + gap: var(--padding-md, 16px); + } + + .header-nav { + flex-wrap: wrap; + gap: var(--padding-sm, 12px); + } + + .header-nav a { + font-size: 13px; + } + + .dropdown-menu { + position: fixed; + left: 50%; + transform: translateX(-50%); + min-width: 200px; + } +} diff --git a/src/components/DashboardLayout.tsx b/src/components/DashboardLayout.tsx new file mode 100644 index 0000000..c658849 --- /dev/null +++ b/src/components/DashboardLayout.tsx @@ -0,0 +1,267 @@ +import { ReactNode, useState } from 'react' +import { Link, useLocation, useNavigate } from 'react-router-dom' +import { useAuth } from '../contexts/AuthContext' +import './DashboardLayout.css' + +interface DashboardLayoutProps { + children: ReactNode +} + +function DashboardLayout({ children }: DashboardLayoutProps) { + const { user, logout } = useAuth() + const location = useLocation() + const navigate = useNavigate() + const [sidebarOpen, setSidebarOpen] = useState(true) + const [rmoMenuOpen, setRmoMenuOpen] = useState(false) + const [concernMenuOpen, setConcernMenuOpen] = useState(false) + const [systemMenuOpen, setSystemMenuOpen] = useState(false) + + const isActive = (path: string) => location.pathname === path + const isActiveParent = (path: string) => location.pathname.startsWith(path) + const isActiveParentMulti = (paths: string[]) => paths.some(path => location.pathname.startsWith(path)) + + // 根据角色判断权限 + const isPolicyholder = user?.role === '投保人' + const isInsurer = user?.role === '保险人' + + // 投保人可见:项目列表、理赔进度、智能工具 + // 保险人可见:询价列表、理赔进度 + + const handleLogout = () => { + logout() + navigate('/login') + } + + return ( +
+ {/* 侧边栏 */} + + + {/* 主内容区 */} +
+ {/* 顶部栏 */} +
+
+ +

RMO

+ 生命科学风险管理 + + +
+
+
+ {user?.name} + {user?.role} +
+ +
+
+ + {/* 页面内容 */} +
+ {children} +
+
+
+ ) +} + +export default DashboardLayout diff --git a/src/components/Footer.css b/src/components/Footer.css new file mode 100644 index 0000000..f28c4d4 --- /dev/null +++ b/src/components/Footer.css @@ -0,0 +1,42 @@ +.footer { + background: #2c3e50; + color: #ecf0f1; + padding: 50px 0 20px; + margin-top: 60px; +} + +.footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 40px; + margin-bottom: 30px; +} + +.footer-section h3 { + font-size: 18px; + margin-bottom: 15px; + color: var(--white); +} + +.footer-section p { + font-size: 14px; + line-height: 1.8; + color: #bdc3c7; + margin-bottom: 8px; +} + +.footer-bottom { + text-align: center; + padding-top: 20px; + border-top: 1px solid #34495e; + color: #95a5a6; + font-size: 14px; +} + +@media (max-width: 768px) { + .footer-content { + grid-template-columns: 1fr; + gap: 30px; + } +} + diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..1142114 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,32 @@ +import './Footer.css' + +function Footer() { + return ( + + ) +} + +export default Footer + diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 0000000..a7d7ba5 --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,337 @@ +/* ========== vdano.com 风格:顶部导航 ========== */ +.header { + position: fixed; + top: 0; + left: 0; + right: 0; + background: var(--white); + border-bottom: 1px solid var(--border-color, #e0e0e0); + z-index: 1000; + height: 56px; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + height: 56px; + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; +} + +.logo { + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; + color: inherit; +} + +.logo h1 { + font-size: 22px; + font-weight: 600; + color: var(--brand-primary); + letter-spacing: -0.02em; +} + +.logo span { + font-size: 13px; + color: var(--text-light, #666); + font-weight: 400; +} + +/* 一级导航:横向排列,等距 */ +.nav { + display: flex; + align-items: center; + gap: 0; + height: 100%; +} + +.nav > a, +.nav .nav-dropdown > a, +.nav .nav-dropdown-trigger { + display: inline-flex; + align-items: center; + height: 100%; + padding: 0 16px; + color: var(--text-color, #333); + font-size: 14px; + font-weight: 500; + text-decoration: none; + transition: color 0.15s ease, background 0.15s ease; + position: relative; + box-sizing: border-box; +} + +.nav > a:hover, +.nav .nav-dropdown:hover > a, +.nav .nav-dropdown:hover .nav-dropdown-trigger { + color: var(--brand-primary); +} + +.nav > a.active, +.nav .nav-dropdown > a.active, +.nav .nav-dropdown-trigger.active { + color: var(--brand-primary); + font-weight: 600; +} + +/* 当前项底部线条(vdano 常见样式) */ +.nav > a.active::after, +.nav .nav-dropdown > a.active::after, +.nav .nav-dropdown-trigger.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 16px; + right: 16px; + height: 2px; + background: var(--brand-primary); +} + +.nav .nav-dropdown { + height: 100%; +} + +.nav .nav-dropdown > a, +.nav .nav-dropdown .nav-dropdown-trigger { + cursor: pointer; +} + +.dropdown-arrow { + font-size: 10px; + margin-left: 4px; + opacity: 0.8; + transition: transform 0.2s ease; +} + +.nav-dropdown:hover .dropdown-arrow { + transform: rotate(180deg); +} + +/* 下拉面板:白底、细边框、左对齐(vdano 风格) */ +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + background: var(--white); + border: 1px solid var(--border-color, #e0e0e0); + border-top: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + padding: 8px 0; + z-index: 1001; +} + +.dropdown-menu a { + display: block; + padding: 10px 20px; + color: var(--text-color, #333); + font-size: 14px; + font-weight: 400; + text-decoration: none; + transition: background 0.15s ease, color 0.15s ease; +} + +.dropdown-menu a:hover { + background: rgba(14, 165, 233, 0.06); + color: var(--brand-primary); +} + +.dropdown-menu a.active { + background: rgba(14, 165, 233, 0.08); + color: var(--brand-primary); + font-weight: 500; +} + +.dropdown-menu a.active::after { + display: none; +} + +/* 三级菜单:风险活动 - 分组标题与子项 */ +.dropdown-menu-level3-wrapper { + min-width: 220px; + padding: 8px 0; +} + +.dropdown-submenu { + padding: 6px 0; + border-bottom: 1px solid var(--border-color, #eee); +} + +.dropdown-submenu:last-of-type { + border-bottom: none; +} + +.dropdown-submenu-title { + padding: 8px 20px 6px; + font-size: 12px; + color: var(--text-light, #888); + font-weight: 600; + letter-spacing: 0.02em; +} + +.dropdown-submenu .dropdown-submenu-title-link { + display: block; + padding: 10px 20px; + font-size: 14px; + font-weight: 400; + color: var(--text-color, #333); +} + +.dropdown-submenu .dropdown-submenu-title-link:hover { + background: rgba(14, 165, 233, 0.06); + color: var(--brand-primary); +} + +.dropdown-submenu a:not(.dropdown-submenu-title-link) { + padding: 8px 20px 8px 28px; + font-size: 13px; + color: var(--text-color, #333); +} + +.dropdown-submenu a:not(.dropdown-submenu-title-link):hover { + background: rgba(14, 165, 233, 0.06); + color: var(--brand-primary); +} + +/* 风险活动触发项(无链接,仅展开) */ +.nav-dropdown-trigger { + color: var(--text-color, #333); + font-size: 14px; + font-weight: 500; + padding: 0 16px; + cursor: default; + user-select: none; +} + +.nav-dropdown-trigger.active::after { + left: 16px; + right: 16px; +} + +/* 登录按钮:vdano 风格多为线框或文字按钮 */ +.login-btn { + margin-left: 16px; + padding: 8px 20px; + font-size: 14px; + font-weight: 500; + color: var(--brand-primary); + background: transparent; + border: 1px solid var(--brand-primary); + border-radius: 4px; + text-decoration: none; + transition: background 0.15s ease, color 0.15s ease; + display: inline-flex; + align-items: center; +} + +.login-btn:hover { + background: var(--brand-primary); + color: var(--white); +} + +.login-btn.active, +.login-btn.active::after { + display: none; +} + +/* 用户信息与退出 */ +.user-menu { + display: flex; + align-items: center; + gap: 12px; + margin-left: 16px; +} + +.user-info-link { + display: flex; + flex-direction: column; + align-items: flex-end; + text-decoration: none; + color: var(--text-color, #333); + padding: 0 8px; +} + +.user-name { + font-size: 14px; + font-weight: 500; + color: var(--text-color, #333); +} + +.user-role { + font-size: 12px; + color: var(--text-light, #666); +} + +.logout-btn-header { + padding: 8px 16px; + font-size: 13px; + font-weight: 500; + color: var(--text-light, #666); + background: transparent; + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 4px; + cursor: pointer; + transition: border-color 0.15s ease, color 0.15s ease; +} + +.logout-btn-header:hover { + border-color: var(--brand-primary); + color: var(--brand-primary); +} + +/* 响应式 */ +@media (max-width: 768px) { + .header { + height: auto; + min-height: 56px; + } + + .header-content { + flex-direction: column; + align-items: stretch; + height: auto; + padding: 12px 16px; + } + + .nav { + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; + height: auto; + } + + .nav > a, + .nav .nav-dropdown > a, + .nav .nav-dropdown-trigger { + height: auto; + padding: 10px 12px; + font-size: 13px; + } + + .logo span { + display: none; + } + + .dropdown-menu { + position: static; + box-shadow: none; + border: 1px solid var(--border-color, #eee); + border-radius: 4px; + margin-top: 4px; + margin-left: 12px; + } + + .login-btn { + margin-left: 0; + margin-top: 8px; + } + + .user-menu { + margin-left: 0; + margin-top: 8px; + } +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..e57cf09 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,159 @@ +import { Link, useLocation, useNavigate } from 'react-router-dom' +import { useState } from 'react' +import { useAuth } from '../contexts/AuthContext' +import './Header.css' + +function Header() { + const location = useLocation() + const navigate = useNavigate() + const { isAuthenticated, user, logout } = useAuth() + const [concernOpen, setConcernOpen] = useState(false) + const [riskDataOpen, setRiskDataOpen] = useState(false) + const [riskActivitiesOpen, setRiskActivitiesOpen] = useState(false) + const [resourceOpen, setResourceOpen] = useState(false) + + const isActive = (path: string) => location.pathname === path + const isActiveParent = (paths: string[]) => paths.some(p => location.pathname.startsWith(p)) + + const handleLogout = () => { + logout() + navigate('/') + } + + return ( +
+
+
+ +

RMO

+ 生命科学风险管理 + + +
+
+
+ ) +} + +export default Header diff --git a/src/components/Layout.css b/src/components/Layout.css new file mode 100644 index 0000000..1ec3d37 --- /dev/null +++ b/src/components/Layout.css @@ -0,0 +1,11 @@ +.layout { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.main-content { + flex: 1; + padding-top: 56px; +} + diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..f2d8f98 --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react' +import Header from './Header' +import Footer from './Footer' +import './Layout.css' + +interface LayoutProps { + children: ReactNode +} + +function Layout({ children }: LayoutProps) { + return ( +
+
+
+ {children} +
+
+
+ ) +} + +export default Layout + diff --git a/src/components/PageContainer.tsx b/src/components/PageContainer.tsx new file mode 100644 index 0000000..bffdd05 --- /dev/null +++ b/src/components/PageContainer.tsx @@ -0,0 +1,24 @@ +import { ReactNode } from 'react' + +interface PageContainerProps { + children: ReactNode + /** 可选:刷新回调,用于规范中的 @refresh */ + onRefresh?: () => void + /** 是否使用紧凑内边距 */ + compact?: boolean +} + +/** + * 页面根容器(规范:所有页面必须使用 PageContainer 包裹) + */ +function PageContainer({ children, compact = false }: PageContainerProps) { + return ( +
+ {children} +
+ ) +} + +export default PageContainer diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx new file mode 100644 index 0000000..ab559a2 --- /dev/null +++ b/src/components/PageHeader.tsx @@ -0,0 +1,37 @@ +import { ReactNode } from 'react' + +interface PageHeaderProps { + title: ReactNode + description?: string + /** 头部右侧操作区 */ + actions?: ReactNode + /** 使用模块首页头部样式(渐变+装饰) */ + variant?: 'default' | 'module' +} + +/** + * 规范:页面头部,统一使用 page-header 或 module-home-header + */ +function PageHeader({ + title, + description, + actions, + variant = 'default', +}: PageHeaderProps) { + const headerClass = + variant === 'module' ? 'module-home-header' : 'page-header' + + return ( +
+
+
+

{title}

+ {description &&

{description}

} +
+ {actions &&
{actions}
} +
+
+ ) +} + +export default PageHeader diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..ec44efd --- /dev/null +++ b/src/components/ProtectedRoute.tsx @@ -0,0 +1,33 @@ +import { Navigate, useLocation } from 'react-router-dom' +import { useAuth } from '../contexts/AuthContext' + +interface ProtectedRouteProps { + children: React.ReactNode +} + +function ProtectedRoute({ children }: ProtectedRouteProps) { + const { isAuthenticated, loading } = useAuth() + const location = useLocation() + + if (loading) { + return ( +
+
加载中...
+
+ ) + } + + if (!isAuthenticated) { + // 保存当前路径,登录后可以跳转回来 + return + } + + return <>{children} +} + +export default ProtectedRoute diff --git a/src/components/QuoteRequestModal.css b/src/components/QuoteRequestModal.css new file mode 100644 index 0000000..c353279 --- /dev/null +++ b/src/components/QuoteRequestModal.css @@ -0,0 +1,219 @@ +.quote-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 16px; + box-sizing: border-box; +} + +.quote-modal { + background: var(--white, #fff); + border-radius: 12px; + max-width: 560px; + width: 100%; + max-height: 90vh; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); +} + +.quote-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border-color, #e5e7eb); + flex-shrink: 0; +} + +.quote-modal-title { + margin: 0; + font-size: 18px; + font-weight: 600; + color: var(--brand-text-default, #111); +} + +.quote-modal-close { + width: 32px; + height: 32px; + border: none; + background: transparent; + font-size: 24px; + line-height: 1; + color: var(--text-color, #555); + cursor: pointer; + border-radius: 6px; + padding: 0; +} + +.quote-modal-close:hover { + background: var(--bg-color, #f3f4f6); + color: var(--brand-text-default, #111); +} + +.quote-modal-body { + padding: 20px; + overflow-y: auto; +} + +.quote-modal-desc { + font-size: 14px; + color: var(--text-color, #555); + margin: 0 0 20px 0; + line-height: 1.5; +} + +.quote-section { + margin-bottom: 24px; +} + +.quote-section:last-child { + margin-bottom: 0; +} + +.quote-section-title { + font-size: 15px; + font-weight: 600; + margin: 0 0 10px 0; + color: var(--brand-text-default, #111); +} + +.quote-section-desc { + font-size: 13px; + color: var(--text-color, #555); + margin: 0 0 12px 0; + line-height: 1.5; +} + +.quote-fill-mode { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 12px; +} + +.quote-radio { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: var(--text-color, #555); + cursor: pointer; +} + +.quote-radio input { + margin: 0; +} + +.quote-form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px 16px; +} + +.quote-modal .form-group { + margin-bottom: 0; +} + +.quote-modal .form-group label { + display: block; + font-size: 13px; + font-weight: 500; + color: var(--text-color, #555); + margin-bottom: 4px; +} + +.quote-modal .form-group input[type="text"], +.quote-modal .form-group input[type="file"] { + width: 100%; + padding: 8px 10px; + font-size: 14px; + border: 1px solid var(--border-color, #e5e7eb); + border-radius: 6px; + box-sizing: border-box; +} + +.form-hint { + font-size: 12px; + color: var(--brand-primary, #0ea5e9); + margin: 4px 0 0 0; +} + +.quote-ai-result { + margin-top: 12px; + padding: 12px; + background: rgba(14, 165, 233, 0.06); + border: 1px solid rgba(14, 165, 233, 0.2); + border-radius: 8px; +} + +.quote-ai-result pre { + margin: 0; + font-size: 13px; + line-height: 1.6; + white-space: pre-wrap; + word-break: break-word; + color: var(--text-color, #555); +} + +.quote-section .btn { + padding: 8px 16px; + font-size: 14px; + border-radius: 8px; + border: none; + cursor: pointer; +} + +.quote-section .btn-primary { + background: var(--brand-primary, #0ea5e9); + color: var(--white, #fff); +} + +.quote-section .btn-primary:hover:not(:disabled) { + background: var(--brand-primary-dark, #0284c7); +} + +.quote-section .btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.quote-precise-tip { + margin-top: 12px; + padding: 12px; + background: var(--bg-color, #f9fafb); + border-radius: 8px; +} + +.quote-precise-tip p { + margin: 0 0 10px 0; + font-size: 13px; + color: var(--text-color, #555); + line-height: 1.5; +} + +.quote-precise-tip .btn-sm { + padding: 6px 12px; + font-size: 13px; +} + +.quote-precise-tip .btn-secondary { + background: var(--white, #fff); + color: var(--text-color, #555); + border: 1px solid var(--border-color, #e5e7eb); +} + +.quote-precise-tip .btn-secondary:hover { + background: var(--bg-color, #f3f4f6); +} + +@media (max-width: 520px) { + .quote-form-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/components/QuoteRequestModal.tsx b/src/components/QuoteRequestModal.tsx new file mode 100644 index 0000000..0858c3d --- /dev/null +++ b/src/components/QuoteRequestModal.tsx @@ -0,0 +1,231 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useQuoteModal } from '../contexts/QuoteModalContext' +import { useAuth } from '../contexts/AuthContext' +import './QuoteRequestModal.css' + +export type QuoteFormData = { + projectCode: string + projectTitle: string + sponsor: string + projectPhase: string +} + +const initialForm: QuoteFormData = { + projectCode: '', + projectTitle: '', + sponsor: '', + projectPhase: '', +} + +function QuoteRequestModal() { + const { isOpen, closeQuoteModal } = useQuoteModal() + const { isAuthenticated } = useAuth() + const navigate = useNavigate() + + const [fillMode, setFillMode] = useState<'manual' | 'upload'>('manual') + const [formData, setFormData] = useState(initialForm) + const [uploading, setUploading] = useState(false) + const [aiQuote, setAiQuote] = useState(null) + const [generatingQuote, setGeneratingQuote] = useState(false) + const [preciseSent, setPreciseSent] = useState(false) + const [sendingPrecise, setSendingPrecise] = useState(false) + + const handleClose = () => { + closeQuoteModal() + setFormData(initialForm) + setAiQuote(null) + setPreciseSent(false) + } + + const handleUploadChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + setUploading(true) + // Mock: AI 识别并解析,自动填充 + await new Promise((r) => setTimeout(r, 1200)) + setFormData({ + projectCode: 'CT-2025-' + Math.floor(1000 + Math.random() * 9000), + projectTitle: file.name.replace(/\.[^.]+$/, '') || '临床试验方案', + sponsor: '示例申办者', + projectPhase: 'I期', + }) + setUploading(false) + } + + const canGenerateQuote = + formData.projectCode.trim() && + formData.projectTitle.trim() && + formData.sponsor.trim() && + formData.projectPhase.trim() + + const handleGenerateQuote = async () => { + if (!canGenerateQuote) return + setGeneratingQuote(true) + // Mock: AI 生成报价 + await new Promise((r) => setTimeout(r, 1500)) + setAiQuote( + `基于当前项目信息(${formData.projectTitle},${formData.projectPhase})的预估报价:\n` + + '· 建议每人保额:80–120 万\n· 每次事故限额:400–600 万\n· 预估年保费区间:约 1.1 万–1.4 万元\n(实际以各保司精准报价为准)' + ) + setGeneratingQuote(false) + } + + const handleGetPreciseQuote = async () => { + if (!isAuthenticated) { + if (window.confirm('获取精准报价需先登录,是否前往登录?')) { + handleClose() + navigate('/login', { state: { from: { pathname: '/dashboard/project-quotes' } } }) + } + return + } + if (!aiQuote) return + setSendingPrecise(true) + // Mock: 系统整合资料发送至各保司 + await new Promise((r) => setTimeout(r, 1000)) + setPreciseSent(true) + setSendingPrecise(false) + } + + if (!isOpen) return null + + return ( +
+
e.stopPropagation()}> +
+

获取报价

+ +
+ +
+

请填写或上传报价所需资料,生成报价后可向各保司获取精准报价。

+ + {/* 第一步:资料 */} +
+

1. 报价需提交的资料

+
+ + +
+ + {fillMode === 'upload' && ( +
+ + + {uploading &&

AI 识别中…

} +
+ )} + +
+
+ + setFormData((d) => ({ ...d, projectCode: e.target.value }))} + placeholder="如:CT-2025-001" + /> +
+
+ + setFormData((d) => ({ ...d, projectTitle: e.target.value }))} + placeholder="试验方案标题" + /> +
+
+ + setFormData((d) => ({ ...d, sponsor: e.target.value }))} + placeholder="申办者名称" + /> +
+
+ + setFormData((d) => ({ ...d, projectPhase: e.target.value }))} + placeholder="如:I期、II期、III期" + /> +
+
+
+ + {/* 第二步:生成报价 */} +
+

2. 生成报价

+ + {aiQuote && ( +
+
{aiQuote}
+
+ )} +
+ + {/* 第三步:获取精准报价 */} +
+

3. 获取精准报价

+

系统将把报价资料整合后以邮件发送至各保司,保司回复后将经临研安审核并回显到报价页面。

+ + {preciseSent && ( +
+

已向各保司发送询价邮件,保司将回复至 rmo@vdano.com,审核通过后将展示在报价页面。

+ {isAuthenticated && ( + + )} +
+ )} +
+
+
+
+ ) +} + +export default QuoteRequestModal diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..2c81440 --- /dev/null +++ b/src/contexts/AuthContext.tsx @@ -0,0 +1,128 @@ +import { createContext, useContext, useState, ReactNode, useEffect } from 'react' + +// 用户角色类型 +export type UserRole = '投保人' | '保险人' + +// 用户信息接口 +export interface User { + id: string + name: string + email: string + role: UserRole + avatar?: string +} + +// AuthContext 接口 +interface AuthContextType { + user: User | null + isAuthenticated: boolean + token: string | null + login: (username: string, password: string) => Promise + logout: () => void + loading: boolean +} + +// 创建 Context +const AuthContext = createContext(undefined) + +// AuthProvider 组件 +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(null) + const [token, setToken] = useState(null) + const [loading, setLoading] = useState(true) + + // 从 localStorage 恢复登录状态 + useEffect(() => { + const savedToken = localStorage.getItem('rmo_token') + const savedUser = localStorage.getItem('rmo_user') + + if (savedToken && savedUser) { + try { + setToken(savedToken) + setUser(JSON.parse(savedUser)) + } catch (error) { + console.error('Failed to restore auth state:', error) + localStorage.removeItem('rmo_token') + localStorage.removeItem('rmo_user') + } + } + setLoading(false) + }, []) + + // 登录函数 + const login = async (username: string, password: string) => { + setLoading(true) + try { + // TODO: 替换为实际的 API 调用 + // const response = await fetch('/api/auth/login', { + // method: 'POST', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify({ username, password }) + // }) + // const data = await response.json() + + // 模拟 API 调用(开发阶段) + await new Promise(resolve => setTimeout(resolve, 500)) + + // 模拟用户数据(根据用户名返回不同角色) + const mockUsers: Record = { + 'policyholder': { + user: { id: '1', name: '投保人', email: 'policyholder@rmo.com', role: '投保人' }, + token: 'mock_token_policyholder' + }, + 'insurer': { + user: { id: '2', name: '保险人', email: 'insurer@rmo.com', role: '保险人' }, + token: 'mock_token_insurer' + }, + 'admin': { + user: { id: '1', name: '投保人', email: 'admin@rmo.com', role: '投保人' }, + token: 'mock_token_admin' + } + } + + const mockData = mockUsers[username.toLowerCase()] || mockUsers['admin'] + + if (password === '123456') { + setUser(mockData.user) + setToken(mockData.token) + localStorage.setItem('rmo_token', mockData.token) + localStorage.setItem('rmo_user', JSON.stringify(mockData.user)) + } else { + throw new Error('用户名或密码错误') + } + } catch (error) { + console.error('Login error:', error) + throw error + } finally { + setLoading(false) + } + } + + // 退出登录函数 + const logout = () => { + setUser(null) + setToken(null) + localStorage.removeItem('rmo_token') + localStorage.removeItem('rmo_user') + } + + const value: AuthContextType = { + user, + isAuthenticated: !!user && !!token, + token, + login, + logout, + loading + } + + return {children} +} + +// 使用 AuthContext 的 Hook +export function useAuth() { + const context = useContext(AuthContext) + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider') + } + return context +} diff --git a/src/contexts/QuoteModalContext.tsx b/src/contexts/QuoteModalContext.tsx new file mode 100644 index 0000000..78216e6 --- /dev/null +++ b/src/contexts/QuoteModalContext.tsx @@ -0,0 +1,30 @@ +import { createContext, useContext, useState, ReactNode, useCallback } from 'react' + +interface QuoteModalContextType { + isOpen: boolean + openQuoteModal: () => void + closeQuoteModal: () => void +} + +const QuoteModalContext = createContext(undefined) + +export function QuoteModalProvider({ children }: { children: ReactNode }) { + const [isOpen, setIsOpen] = useState(false) + + const openQuoteModal = useCallback(() => setIsOpen(true), []) + const closeQuoteModal = useCallback(() => setIsOpen(false), []) + + return ( + + {children} + + ) +} + +export function useQuoteModal() { + const context = useContext(QuoteModalContext) + if (context === undefined) { + throw new Error('useQuoteModal must be used within a QuoteModalProvider') + } + return context +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..b51bd0d --- /dev/null +++ b/src/index.css @@ -0,0 +1,143 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* 设计规范:设计令牌与品牌色 */ +:root { + /* 品牌主色:科技蓝 */ + --brand-primary: #0ea5e9; + --brand-primary-rgb: 14, 165, 233; + --brand-primary-dark: #0284c7; + --brand-primary-light: #38bdf8; + --brand-primary-lighter: #7dd3fc; + --brand-text-default: #333333; + --brand-text-active: #ffffff; + + /* 兼容别名 */ + --primary-color: var(--brand-primary); + --primary-hover: var(--brand-primary-dark); + --secondary-color: #059669; + --text-color: var(--brand-text-default); + --text-light: #666; + --bg-color: #f5f5f5; + --white: #ffffff; + --border-color: #e8e8e8; + --shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + --shadow-hover: 0 8px 25px rgba(0, 0, 0, 0.15); + + /* 间距规范(规范:默认 16px) */ + --padding-xs: 8px; + --padding-sm: 12px; + --padding-md: 16px; + --padding-lg: 20px; + --padding-xl: 24px; + + /* 页面布局高度变量 */ + --navbar-height: 80px; + --page-container-padding: 24px; + --page-header-height: 50px; + --page-header-margin-bottom: 16px; + --page-content-height: calc(100vh - var(--navbar-height) - var(--page-container-padding)); + --page-body-height: calc(var(--page-content-height) - var(--page-header-height) - var(--page-header-margin-bottom)); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', + 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; + color: var(--text-color); + line-height: 1.6; + background-color: var(--bg-color); +} + +a { + text-decoration: none; + color: inherit; +} + +ul { + list-style: none; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +.section { + padding: 60px 0; +} + +.section-title { + font-size: 32px; + font-weight: 600; + text-align: center; + margin-bottom: 20px; + color: var(--text-color); +} + +.section-subtitle { + font-size: 18px; + text-align: center; + color: var(--text-light); + margin-bottom: 50px; +} + +.card { + background: var(--white); + border-radius: 8px; + padding: 30px; + box-shadow: var(--shadow); + transition: all 0.3s ease; +} + +/* 规范:禁止 translateY,使用阴影与亮度 */ +.card:hover { + box-shadow: var(--shadow-hover); + filter: brightness(1.02); + border-color: rgba(var(--brand-primary-rgb), 0.2); +} + +.btn { + display: inline-block; + padding: 12px 30px; + background: var(--primary-color); + color: var(--white); + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: all 0.3s ease; +} + +.btn:hover { + background: var(--primary-hover); + box-shadow: var(--shadow); + filter: brightness(1.05); +} + +.btn-secondary { + background: var(--secondary-color); +} + +.btn-secondary:hover { + background: #10b981; + filter: brightness(1.05); +} + +@media (max-width: 768px) { + .section { + padding: 40px 0; + } + + .section-title { + font-size: 24px; + } + + .container { + padding: 0 15px; + } +} + diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..b042fd4 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import { AuthProvider } from './contexts/AuthContext' +import './index.css' +import './styles/common.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +) + diff --git a/src/pages/Company.css b/src/pages/Company.css new file mode 100644 index 0000000..5987a38 --- /dev/null +++ b/src/pages/Company.css @@ -0,0 +1,147 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.company-intro { + margin-bottom: 40px; +} + +.company-intro h2 { + font-size: 28px; + margin-bottom: 20px; + color: var(--primary-color); +} + +.company-intro h3 { + font-size: 22px; + margin: 30px 0 15px; + color: var(--text-color); +} + +.company-intro p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 20px; +} + +.advantage-list { + list-style: none; + padding: 0; +} + +.advantage-list li { + padding: 12px 0; + font-size: 16px; + color: var(--text-color); +} + +.advantage-list a { + color: var(--primary-color); + text-decoration: none; +} + +.advantage-list a:hover { + text-decoration: underline; +} + +.activity-section { + background: var(--bg-color); +} + +.activity-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 30px; +} + +.activity-card { + position: relative; +} + +.activity-date { + position: absolute; + top: -15px; + right: 20px; + background: var(--primary-color); + color: var(--white); + padding: 8px 15px; + border-radius: 20px; + font-size: 14px; + font-weight: 600; +} + +.activity-card h3 { + font-size: 20px; + margin-bottom: 15px; + color: var(--text-color); +} + +.activity-card p { + font-size: 14px; + color: var(--text-light); + line-height: 1.6; +} + +.resource-section { + background: var(--white); +} + +.resource-tabs { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; +} + +.resource-category { + background: var(--bg-color); + padding: 30px; + border-radius: 8px; +} + +.resource-category h3 { + font-size: 20px; + margin-bottom: 20px; + color: var(--primary-color); +} + +.resource-list { + list-style: none; + padding: 0; +} + +.resource-list li { + padding: 10px 0; + font-size: 15px; + color: var(--text-color); + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: color 0.3s ease; +} + +.resource-list li:hover { + color: var(--primary-color); +} + +.resource-list li:last-child { + border-bottom: none; +} + +@media (max-width: 768px) { + .activity-grid, + .resource-tabs { + grid-template-columns: 1fr; + } +} + diff --git a/src/pages/Company.tsx b/src/pages/Company.tsx new file mode 100644 index 0000000..2d257ef --- /dev/null +++ b/src/pages/Company.tsx @@ -0,0 +1,130 @@ +import './Company.css' + +function Company() { + return ( +
+
+
+

关于临研安

+

专业的临床试验风险管理服务提供商

+
+
+ +
+
+
+

关于RMO

+

+ RMO(Risk Management Organization,风险管理组织)是专注于生物医药风险管理的组织, + 由众多保司、华泰保险经纪、上海临研安信息科技有限公司(以下简称"临研安")、 + 北京药盾公益基金会(以下简称"药盾")共同组成。 +

+

+ 当保单生效时,即意味着上述RMO将为您启动、提供全面的风险管理服务项目。 + 临研安作为该RMO服务的对外窗口,您的诉求均可向临研安提出; + 临研安负责协调RMO组织内的各方为您提供高质量的风险管理服务。 +

+

组织成员

+
    +
  • 临研安:专业的风险管理能力,作为RMO服务的对外窗口
  • +
  • 华泰保险经纪:保险经纪服务,30年医疗直付服务经验
  • +
  • 保险公司:提供保险保障服务
  • +
  • 药盾基金会:公益支持与保障
  • +
+

核心优势

+
    +
  • ✓ 专业的风险管理团队,具备临床试验、医学、法律背景
  • +
  • ✓ 与华泰保险经纪深度合作,提供全面的保险服务
  • +
  • ✓ 30年医疗直付服务经验,覆盖全国主要医院
  • +
  • ✓ 快速响应机制,24小时内处理紧急情况
  • +
  • ✓ 完善的风险减量和外溢风险管理服务
  • +
  • ✓ 标准化流程,确保每个案件都得到规范处理
  • +
+

联系我们

+

+ 除日常的工作群(微信群/钉钉群)外,您可以通过以下方式联系我们: +

+ +

+ 您可以通过以上方式向我们提供建议、反馈。非常感谢您的宝贵意见。 +

+
+
+
+ +
+
+

活动动态

+
+
+
2024-12
+

行业研讨会

+

参加中国临床试验风险管理高峰论坛,分享RMO模式实践经验

+
+
+
2024-11
+

产品发布会

+

正式发布RMO一站式风险管理解决方案

+
+
+
2024-10
+

展会参与

+

参加中国国际医疗器械博览会,展示风险管理服务能力

+
+
+
2024-09
+

培训会

+

组织临床试验风险管理培训,提升行业专业水平

+
+
+
+
+ +
+
+

资源中心

+
+
+

📋 法律法规

+
    +
  • 《药物临床试验质量管理规范》
  • +
  • 《医疗器械临床试验质量管理规范》
  • +
  • 《药品管理法》相关条款
  • +
+
+
+

📊 团体标准

+
    +
  • 临床试验风险管理团体标准
  • +
  • 受试者损害救济标准
  • +
  • 保险理赔标准
  • +
+
+
+

🤝 行业共识

+
    +
  • 临床试验风险管理共识
  • +
  • 受试者权益保护共识
  • +
  • 保险保障机制共识
  • +
+
+
+

🎥 视频资料

+
    +
  • RMO模式介绍视频
  • +
  • 风险管理培训视频
  • +
  • 案例分享视频
  • +
+
+
+
+
+
+ ) +} + +export default Company + diff --git a/src/pages/DrugSafetyDict.tsx b/src/pages/DrugSafetyDict.tsx new file mode 100644 index 0000000..bc8785a --- /dev/null +++ b/src/pages/DrugSafetyDict.tsx @@ -0,0 +1,28 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './RiskData.css' + +function DrugSafetyDict() { + return ( + +
+ +
+
+
+
+

建设中

+
+
+
+
+
+
+ ) +} + +export default DrugSafetyDict diff --git a/src/pages/FAQ.css b/src/pages/FAQ.css new file mode 100644 index 0000000..f1fd62c --- /dev/null +++ b/src/pages/FAQ.css @@ -0,0 +1,38 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.faq-list { + margin: 30px 0; +} + +.faq-item { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + margin-bottom: 15px; +} + +.faq-item h3 { + font-size: 16px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.faq-item p { + font-size: 14px; + color: var(--text-color); + line-height: 1.6; + margin: 0; +} diff --git a/src/pages/FAQ.tsx b/src/pages/FAQ.tsx new file mode 100644 index 0000000..50cb875 --- /dev/null +++ b/src/pages/FAQ.tsx @@ -0,0 +1,73 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './FAQ.css' + +function FAQ() { + return ( + +
+ +
+
+
+
+

保险相关问题

+
+
+

Q: 保险保障范围包括哪些?

+

A: 保险主要保障SUSAR、SAE等首要风险,包括身故、残疾赔偿金等。具体保障范围请参考保险合同条款。

+
+
+

Q: 理赔申请需要多长时间?

+

A: 正常情况下,在收集到完整资料后,7-10个工作日内完成详细评估,5个工作日内提出赔偿方案建议。

+
+
+

Q: 如何申请理赔?

+

A: 您可以通过医院端、申办者端或CRO报案。具体流程请参考理赔服务流程页面。

+
+
+ +

保证方案问题

+
+
+

Q: 专项风险管理基金如何设立?

+

A: 申办者根据项目大小、历史或行业经验,向华泰经纪支付风险管理费(例如一个项目3-5万),双方约定使用范围、对象、执行和审批流程等要素。

+
+
+

Q: 风险减量服务包括哪些内容?

+

A: 包括风险点检查、人员培训、方案完善建议等,帮助主动识别和降低风险。

+
+
+

Q: 外溢风险管理服务的响应时间是多少?

+

A: 正常情况下24小时内与医院、受试者沟通,并通知申办者。紧急案件立即响应。

+
+
+ +

服务流程问题

+
+
+

Q: 如何联系RMO服务团队?

+

A: 除日常的工作群(微信群/钉钉群)外,您可以通过邮件 rmo@vdanno.com 或电话 4009 606 520 联系我们。

+
+
+

Q: 服务响应时效如何?

+

A: 日常咨询24小时内回复,紧急案件立即响应,跨学科问题各方沟通明确后统一回复。

+
+
+

Q: 如何跟踪案件处理进展?

+

A: 您可以通过沟通群或电话咨询案件处理进展,我们会及时向您通报案件处理状态。

+
+
+
+
+
+
+
+
+ ) +} + +export default FAQ diff --git a/src/pages/Holder.css b/src/pages/Holder.css new file mode 100644 index 0000000..dbc342b --- /dev/null +++ b/src/pages/Holder.css @@ -0,0 +1,49 @@ +.responsibility-overview { + list-style: none; + padding-left: 0; + margin: 15px 0 0; +} + +.responsibility-overview li { + padding: 8px 0; + padding-left: 1.5em; + position: relative; + line-height: 1.6; +} + +.responsibility-overview li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--primary-color); +} + +.content-section { + margin: 20px 0; +} + +.content-section h3 { + font-size: 22px; + margin: 30px 0 15px; + color: var(--primary-color); +} + +.content-section p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 20px; +} + +.content-section ul { + list-style: none; + padding-left: 20px; + margin: 15px 0; +} + +.content-section li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); + line-height: 1.6; +} diff --git a/src/pages/Holder.tsx b/src/pages/Holder.tsx new file mode 100644 index 0000000..46c3bf7 --- /dev/null +++ b/src/pages/Holder.tsx @@ -0,0 +1,63 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './Holder.css' + +function Holder() { + return ( + +
+ +
+
+
+
+

持有人职责概述

+
    +
  • 建立药物警戒体系,开展上市后安全性监测
  • +
  • 收集、评价和报告药品不良反应
  • +
  • 开展药品上市后研究,持续评估风险与获益
  • +
  • 制定并实施药品风险管理计划
  • +
+
+
+
+ +
+
+
+

持有人核心职责

+
+

上市后药物安全

+

+ 药品上市许可持有人(MAH)是药品安全的责任主体,对药品全生命周期质量安全承担主要责任。 + 持有人需建立并持续完善上市后药物安全监测与风险管理体系,确保药品上市后的安全使用。 +

+

持有人主要职责

+
    +
  • ✓ 建立药物警戒体系,开展上市后安全性监测
  • +
  • ✓ 收集、评价和报告药品不良反应及其他与用药有关的有害反应
  • +
  • ✓ 开展药品上市后研究,持续评估药品风险与获益
  • +
  • ✓ 制定并实施药品风险管理计划,及时采取风险控制措施
  • +
  • ✓ 按规定报告、处置药品质量问题和召回等
  • +
+

RMO模式如何支持持有人

+
    +
  • ✓ 协助建立和完善药物警戒与风险管理体系
  • +
  • ✓ 提供上市后安全监测与报告的专业支持
  • +
  • ✓ 协助开展风险识别、评估与沟通
  • +
  • ✓ 提供保险与保证方案,转移上市后责任风险
  • +
+
+
+
+
+
+
+
+ ) +} + +export default Holder diff --git a/src/pages/Home.css b/src/pages/Home.css new file mode 100644 index 0000000..1913426 --- /dev/null +++ b/src/pages/Home.css @@ -0,0 +1,438 @@ +.hero { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 120px 0 80px; + text-align: center; +} + +.hero-compact { + padding: 60px 0 40px; +} + +.hero-content { + max-width: 800px; + margin: 0 auto; +} + +.hero-title { + font-size: 48px; + font-weight: 700; + margin-bottom: 20px; +} + +.hero-subtitle { + font-size: 20px; + margin-bottom: 40px; + opacity: 0.9; +} + +.hero-buttons { + display: flex; + gap: 20px; + justify-content: center; + flex-wrap: wrap; +} + +.hero-buttons .btn-quote { + background: rgba(255, 255, 255, 0.95); + color: var(--brand-primary, #0ea5e9); + border: 2px solid rgba(255, 255, 255, 0.95); +} + +.hero-buttons .btn-quote:hover { + background: var(--white); + color: var(--brand-primary-dark, #0284c7); + border-color: var(--white); +} + +/* 智能工具快捷入口 - 页面中部左侧 */ +.home-quick-tools { + position: fixed; + left: 24px; + top: 50%; + transform: translateY(-50%); + z-index: 100; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} + +.home-quick-tool-icon { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + width: 120px; + padding: 12px; + background: var(--white); + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); + text-decoration: none; + color: var(--text-color); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.home-quick-tool-icon:hover { + transform: scale(1.04); + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18); +} + +.home-quick-tool-icon img { + width: 88px; + height: auto; + display: block; + border-radius: 8px; +} + +.home-quick-tool-icon .quick-tool-emoji { + font-size: 48px; + line-height: 1; +} + +.home-quick-tool-icon .quick-tool-label { + font-size: 13px; + font-weight: 500; + text-align: center; + line-height: 1.3; +} + +.logic-section { + background: var(--bg-color); + padding: 80px 0; +} + +.logic-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 40px; + margin-top: 40px; +} + +.logic-card { + background: var(--white); + border-radius: 12px; + padding: 40px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.logic-card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.logic-header { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 25px; + padding-bottom: 20px; + border-bottom: 2px solid var(--border-color); +} + +.logic-icon { + font-size: 48px; +} + +.logic-header h3 { + font-size: 24px; + color: var(--primary-color); + margin: 0; +} + +.logic-body p { + font-size: 16px; + color: var(--text-color); + line-height: 1.6; + margin-bottom: 20px; +} + +.logic-list { + list-style: none; + padding: 0; + margin: 0; +} + +.logic-list li { + padding: 12px 0; + padding-left: 25px; + position: relative; + font-size: 15px; + color: var(--text-color); + line-height: 1.6; +} + +.logic-list li::before { + content: "✓"; + position: absolute; + left: 0; + color: var(--primary-color); + font-weight: bold; + font-size: 18px; +} + +.action-steps { + display: flex; + flex-direction: column; + gap: 20px; +} + +.action-step { + display: flex; + gap: 20px; + align-items: flex-start; + padding: 20px; + background: var(--bg-color); + border-radius: 8px; + transition: background 0.3s ease; +} + +.action-step:hover { + background: rgba(var(--brand-primary-rgb), 0.05); +} + +.step-number { + width: 40px; + height: 40px; + background: var(--primary-color); + color: var(--white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 18px; + flex-shrink: 0; +} + +.step-content { + flex: 1; +} + +.step-content h4 { + font-size: 18px; + color: var(--text-color); + margin: 0 0 8px 0; +} + +.step-content p { + font-size: 14px; + color: var(--text-light); + margin: 0; + line-height: 1.5; +} + +.model-section { + background: var(--white); +} + +.model-diagram { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + margin-top: 40px; +} + +.model-card { + background: var(--bg-color); + padding: 30px; + border-radius: 12px; + text-align: center; + min-width: 200px; + flex: 1; + max-width: 250px; +} + +a.model-card { + text-decoration: none; + color: inherit; + display: flex; + flex-direction: column; + align-items: center; + transition: background 0.2s ease, box-shadow 0.2s ease; +} + +a.model-card:hover { + background: rgba(14, 165, 233, 0.08); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.model-icon { + font-size: 48px; + margin-bottom: 15px; +} + +.model-card h3 { + font-size: 20px; + margin-bottom: 10px; + color: var(--text-color); +} + +.model-card p { + font-size: 14px; + color: var(--text-light); +} + +.model-arrow { + font-size: 32px; + color: var(--primary-color); + font-weight: bold; +} + +.intro-section { + background: var(--bg-color); +} + +.intro-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; +} + +.intro-grid .card h3 { + font-size: 24px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.value-list { + list-style: none; + padding: 0; +} + +.value-list li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.nav-section { + background: var(--white); +} + +.nav-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin-top: 40px; +} + +.nav-card { + background: var(--white); + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 40px 30px; + text-align: center; + transition: all 0.3s ease; + display: block; +} + +.nav-card:hover { + border-color: rgba(var(--brand-primary-rgb), 0.3); + box-shadow: var(--shadow-hover); + filter: brightness(1.02); +} + +.nav-icon { + font-size: 64px; + margin-bottom: 20px; +} + +.nav-card h3 { + font-size: 22px; + margin-bottom: 15px; + color: var(--text-color); +} + +.nav-card p { + font-size: 14px; + color: var(--text-light); +} + +@media (max-width: 768px) { + .hero-title { + font-size: 32px; + } + + .hero-subtitle { + font-size: 16px; + } + + .hero-buttons { + flex-direction: column; + align-items: center; + } + + .model-diagram { + flex-direction: column; + } + + .model-arrow { + transform: rotate(90deg); + } + + .intro-grid, + .nav-grid { + grid-template-columns: 1fr; + } + + .logic-content { + grid-template-columns: 1fr; + gap: 30px; + } + + .logic-card { + padding: 30px 20px; + } + + .logic-header { + flex-direction: column; + text-align: center; + gap: 10px; + } + + .logic-header h3 { + font-size: 20px; + } + + .action-step { + flex-direction: column; + text-align: center; + gap: 15px; + } + + .step-number { + margin: 0 auto; + } + + .home-quick-tools { + position: static; + transform: none; + left: auto; + flex-direction: row; + justify-content: center; + gap: 16px; + margin: 16px 0; + padding: 0 16px; + } + + .home-quick-tool-icon { + width: 100px; + } + + .home-quick-tool-icon:hover { + transform: scale(1.02); + } + + .home-quick-tool-icon img { + width: 72px; + } + + .home-quick-tool-icon .quick-tool-emoji { + font-size: 40px; + } +} + diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000..246463d --- /dev/null +++ b/src/pages/Home.tsx @@ -0,0 +1,157 @@ +import { Link } from 'react-router-dom' +import PageContainer from '../components/PageContainer' +import { useQuoteModal } from '../contexts/QuoteModalContext' +import './Home.css' + +function Home() { + const { openQuoteModal } = useQuoteModal() + + return ( + +
+ {/* Hero Section */} +
+
+
+

生命科学风险管理的保险与保证方案

+
+ 保险方案 + 保证方案 + +
+
+
+
+ + {/* 智能工具快捷入口 - 页面中部左侧 */} +
+ + 📋 + 全部保司报价 + + + 上传方案进行评估 + 方案风险评估 + + + 🔍 + 药安查 + +
+ + {/* 风险管理逻辑区域 */} +
+
+

风险管理业务

+
+ {/* 基于数据,发现分析 */} +
+
+
📈
+

安全数据服务

+
+
+

通过系统化的数据收集、监测和分析,识别潜在风险信号,为风险管理决策提供科学依据。

+
    +
  • 数据收集与整合:多渠道数据源整合,建立统一的风险数据库
  • +
  • 信号检测:运用统计学方法和数据挖掘技术,及时发现异常信号
  • +
  • 风险分析:深入分析风险特征、发生频率和严重程度
  • +
  • 趋势评估:持续监测风险变化趋势,预测潜在风险
  • +
+
+
+ + {/* 管理行动 */} +
+
+
⚙️
+

风险管理业务

+
+
+

基于风险分析结果,采取系统化的管理行动,形成完整的风险管理闭环。

+
    +
  • 风险预防:制定预防性措施,从源头降低风险发生概率
  • +
  • 措施执行:落实风险控制措施,确保各项管理要求有效实施
  • +
  • 效果评估:定期评估风险管理措施的有效性,监测风险控制效果
  • +
  • 改进完善:根据评估结果持续优化风险管理策略,提升管理水平
  • +
+
+
+
+
+
+ + {/* 风险管理体系区域 */} +
+
+

风险管理体系

+
+ +
📜
+

法律法规

+

临床试验风险主体,承担主要责任

+ +
+
+
📚
+

实践指南

+

协助风险管理,保障受试者安全

+
+
+
+
📊
+

行业动态

+

风险提示与行业信息

+
+
+
+
🔍
+

药物警戒

+

全面保障,确保安全

+
+
+
+
+ + {/* 快速导航 */} +
+
+

各方职责

+
+ +
💼
+

申办者职责

+

风险管理体系

+ + +
📋
+

持有人职责

+

负责上市后药物安全

+ + +
🏥
+

研究中心

+

机构、研究者、伦理委员会支持

+ + +
👤
+

受试者专区

+

临床试验介绍、权益保障、损害救济

+ + +
🤝
+

CXO职责

+

CRO、CDMO、SMO支持服务

+ +
+
+
+
+
+ ) +} + +export default Home + diff --git a/src/pages/Institution.css b/src/pages/Institution.css new file mode 100644 index 0000000..971b4a8 --- /dev/null +++ b/src/pages/Institution.css @@ -0,0 +1,125 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.responsibility-overview { + list-style: none; + padding-left: 0; + margin: 15px 0 0; +} + +.responsibility-overview li { + padding: 8px 0; + padding-left: 1.5em; + position: relative; + line-height: 1.6; +} + +.responsibility-overview li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--primary-color); +} + +.content-section { + margin: 20px 0; +} + +.content-section h3 { + font-size: 22px; + margin: 30px 0 15px; + color: var(--primary-color); +} + +.content-section p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 20px; +} + +.content-section ul { + list-style: none; + padding-left: 20px; + margin: 15px 0; +} + +.content-section li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); + line-height: 1.6; +} + +.support-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin: 30px 0; +} + +.support-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.support-item h4 { + font-size: 18px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.support-item p { + font-size: 15px; + color: var(--text-color); + margin: 0; + line-height: 1.6; +} + +.guarantee-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 25px; + margin: 30px 0; +} + +.guarantee-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; +} + +.guarantee-item h4 { + font-size: 20px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.guarantee-item p { + font-size: 15px; + color: var(--text-color); + line-height: 1.8; + margin: 0; +} + +@media (max-width: 768px) { + .support-grid, + .guarantee-list { + grid-template-columns: 1fr; + } +} + diff --git a/src/pages/Institution.tsx b/src/pages/Institution.tsx new file mode 100644 index 0000000..25cee78 --- /dev/null +++ b/src/pages/Institution.tsx @@ -0,0 +1,92 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './Institution.css' + +function Institution() { + return ( + +
+ +
+
+
+
+

研究中心职责概述

+
    +
  • 协助风险管理,保障受试者安全
  • +
  • 研究者负责临床试验的执行
  • +
  • 伦理委员会负责伦理审查
  • +
  • 及时报告不良事件
  • +
+
+
+
+ +
+
+
+

试验机构关注要点

+
+

与受试者安全相关的内容

+
    +
  • ✓ 受试者安全监测和报告机制
  • +
  • ✓ 不良事件的及时识别和处理
  • +
  • ✓ 医疗救治的及时性和有效性
  • +
  • ✓ 风险预警和应急响应
  • +
+

机构职责说明

+

+ 试验机构作为临床试验的执行主体,需要协助申办者进行风险管理, + 确保试验过程中受试者的安全。机构应建立完善的安全监测体系, + 及时报告不良事件,配合RMO模式的风险管理服务。 +

+ +
+
+
+
+ +
+
+
+

研究者支持

+
+

研究者职责

+
    +
  • 负责临床试验的具体执行
  • +
  • 监测受试者安全,及时识别不良事件
  • +
  • 按照方案要求进行医疗救治
  • +
  • 及时报告严重不良事件和SUSAR
  • +
  • 保障受试者权益
  • +
+
+
+
+
+ +
+
+
+

伦理委员会支持

+
+

伦理审查相关支持

+

+ RMO模式为伦理委员会提供专业的风险管理评估支持,帮助伦理委员会更好地评估 + 临床试验的风险和保障措施,确保受试者权益得到充分保障。 +

+ +
+
+
+
+
+
+
+ ) +} + +export default Institution + diff --git a/src/pages/Login.css b/src/pages/Login.css new file mode 100644 index 0000000..67c7292 --- /dev/null +++ b/src/pages/Login.css @@ -0,0 +1,135 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.login-card { + max-width: 400px; + margin: 0 auto; + padding: 40px; +} + +.login-card h2 { + font-size: 28px; + margin-bottom: 30px; + text-align: center; + color: var(--primary-color); +} + +.login-form { + display: flex; + flex-direction: column; + gap: 20px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.form-group label { + font-size: 14px; + color: var(--text-color); + font-weight: 500; +} + +.form-group input { + padding: 12px; + border: 1px solid var(--border-color); + border-radius: 8px; + font-size: 14px; + transition: border-color 0.3s ease; +} + +.form-group input:focus { + outline: none; + border-color: var(--primary-color); +} + +.form-options { + display: flex; + justify-content: space-between; + align-items: center; +} + +.checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.checkbox-label input { + cursor: pointer; +} + +.forgot-link { + color: var(--primary-color); + text-decoration: none; + font-size: 14px; +} + +.forgot-link:hover { + text-decoration: underline; +} + +.btn-primary { + background: var(--primary-color); + color: var(--white); + padding: 12px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.3s ease; +} + +.btn-primary:hover { + background: var(--primary-hover); +} + +.login-footer { + margin-top: 20px; + text-align: center; + font-size: 14px; + color: var(--text-light); +} + +.login-footer a { + color: var(--primary-color); + text-decoration: none; +} + +.login-footer a:hover { + text-decoration: underline; +} + +.error-message { + background: #fee; + color: #c33; + padding: var(--padding-md, 16px); + border-radius: 4px; + margin-bottom: var(--padding-md, 16px); + border: 1px solid #fcc; +} + +.form-hint { + margin-top: 4px; + color: var(--text-light, #666); +} + +.form-hint small { + font-size: 12px; +} diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 0000000..1e67c32 --- /dev/null +++ b/src/pages/Login.tsx @@ -0,0 +1,106 @@ +import { useState, FormEvent } from 'react' +import { useNavigate, useLocation } from 'react-router-dom' +import { useAuth } from '../contexts/AuthContext' +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './Login.css' + +function Login() { + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + const { login, isAuthenticated } = useAuth() + const navigate = useNavigate() + const location = useLocation() + + // 如果已登录,重定向到工作台 + if (isAuthenticated) { + navigate('/dashboard', { replace: true }) + return null + } + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() + setError('') + setLoading(true) + + try { + await login(username, password) + // 登录成功,跳转到之前访问的页面或工作台 + const from = (location.state as { from?: Location })?.from?.pathname || '/dashboard' + navigate(from, { replace: true }) + } catch (err) { + setError(err instanceof Error ? err.message : '登录失败,请检查用户名和密码') + } finally { + setLoading(false) + } + } + + return ( + +
+ +
+
+
+
+

用户登录

+
+ {error &&
{error}
} +
+ + setUsername(e.target.value)} + required + disabled={loading} + /> +
+ 测试账号:admin, insurer, broker, tpa, patient, institution, cxo(密码:123456) +
+
+
+ + setPassword(e.target.value)} + required + disabled={loading} + /> +
+
+ + 忘记密码? +
+ +
+
+

还没有账号?请联系管理员创建账号

+
+
+
+
+
+
+
+ ) +} + +export default Login diff --git a/src/pages/Overseas.css b/src/pages/Overseas.css new file mode 100644 index 0000000..e70feff --- /dev/null +++ b/src/pages/Overseas.css @@ -0,0 +1,40 @@ +.overseas .building-notice { + text-align: center; + font-size: 1.25rem; + color: var(--text-secondary, #666); + margin: 2rem 0; +} + +.overseas .value-list { + list-style: none; + padding: 0; + margin: 16px 0; +} + +.overseas .value-list li { + padding: 8px 0; + padding-left: 20px; + position: relative; +} + +.overseas .value-list li::before { + content: '✓'; + position: absolute; + left: 0; + color: var(--brand-primary); + font-weight: 600; +} + +.overseas .contact-note { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid var(--border-color); +} + +.overseas .contact-note a { + color: var(--brand-primary); +} + +.overseas .contact-note a:hover { + text-decoration: underline; +} diff --git a/src/pages/Overseas.tsx b/src/pages/Overseas.tsx new file mode 100644 index 0000000..d5ab7cc --- /dev/null +++ b/src/pages/Overseas.tsx @@ -0,0 +1,28 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './Overseas.css' + +function Overseas() { + return ( + +
+ +
+
+
+
+

建设中

+
+
+
+
+
+
+ ) +} + +export default Overseas diff --git a/src/pages/PVReport.tsx b/src/pages/PVReport.tsx new file mode 100644 index 0000000..1efea46 --- /dev/null +++ b/src/pages/PVReport.tsx @@ -0,0 +1,28 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './RiskData.css' + +function PVReport() { + return ( + +
+ +
+
+
+
+

建设中

+
+
+
+
+
+
+ ) +} + +export default PVReport diff --git a/src/pages/Participant.css b/src/pages/Participant.css new file mode 100644 index 0000000..57c32f1 --- /dev/null +++ b/src/pages/Participant.css @@ -0,0 +1,238 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.responsibility-overview { + list-style: none; + padding-left: 0; + margin: 15px 0 0; +} + +.responsibility-overview li { + padding: 8px 0; + padding-left: 1.5em; + position: relative; + line-height: 1.6; +} + +.responsibility-overview li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--primary-color); +} + +.content-section { + margin: 20px 0; +} + +.content-section h3 { + font-size: 22px; + margin: 30px 0 15px; + color: var(--primary-color); +} + +.content-section p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 20px; +} + +.content-section ul { + list-style: none; + padding-left: 20px; + margin: 15px 0; +} + +.content-section li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.process-flow { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + margin: 30px 0; +} + +.flow-step { + text-align: center; + min-width: 120px; +} + +.flow-number { + width: 50px; + height: 50px; + background: var(--primary-color); + color: var(--white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + font-weight: bold; + margin: 0 auto 10px; +} + +.flow-step p { + font-size: 14px; + color: var(--text-color); + margin: 0; +} + +.flow-arrow { + font-size: 24px; + color: var(--primary-color); + font-weight: bold; +} + +.rights-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin: 30px 0; +} + +.right-item { + text-align: center; + padding: 30px 20px; + background: var(--bg-color); + border-radius: 12px; + transition: all 0.3s ease; +} + +.right-item:hover { + box-shadow: var(--shadow-hover); + filter: brightness(1.02); +} + +.right-icon { + font-size: 48px; + margin-bottom: 15px; +} + +.right-item h3 { + font-size: 20px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.right-item p { + font-size: 15px; + color: var(--text-color); + line-height: 1.6; + margin: 0; +} + +.compensation-principles { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 25px; + margin: 30px 0; +} + +.principle-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.principle-item h4 { + font-size: 18px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.principle-item p { + font-size: 15px; + color: var(--text-color); + line-height: 1.8; + margin: 0; +} + +.relief-process { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 25px; + margin: 30px 0; +} + +.process-item { + text-align: center; + padding: 25px; + background: var(--bg-color); + border-radius: 8px; +} + +.process-icon { + width: 50px; + height: 50px; + background: var(--primary-color); + color: var(--white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + font-weight: bold; + margin: 0 auto 15px; +} + +.process-item h4 { + font-size: 18px; + margin-bottom: 10px; + color: var(--text-color); +} + +.process-item p { + font-size: 14px; + color: var(--text-light); + margin: 0; +} + +.contact-info { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + margin-top: 20px; +} + +.contact-info p { + font-size: 16px; + color: var(--text-color); + margin: 10px 0; +} + +@media (max-width: 768px) { + .process-flow { + flex-direction: column; + } + + .flow-arrow { + transform: rotate(90deg); + } + + .rights-grid, + .compensation-principles, + .relief-process { + grid-template-columns: 1fr; + } +} + diff --git a/src/pages/Participant.tsx b/src/pages/Participant.tsx new file mode 100644 index 0000000..cf34bff --- /dev/null +++ b/src/pages/Participant.tsx @@ -0,0 +1,188 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './Participant.css' + +function Participant() { + return ( + +
+ +
+
+
+
+

受试者专区概述

+
    +
  • 了解临床试验基本知识
  • +
  • 知情同意与权益保障
  • +
  • 损害救济与补偿机制
  • +
+
+
+
+ +
+
+
+

临床试验介绍

+
+

什么是临床试验

+

+ 临床试验是指在人体(病人或健康志愿者)进行的系统性研究, + 以证实或揭示试验药物的作用、不良反应及/或试验药物的吸收、分布、代谢和排泄, + 目的是确定试验药物的疗效与安全性。 +

+

参与临床试验的意义

+
    +
  • ✓ 为医学进步做出贡献
  • +
  • ✓ 可能获得新的治疗机会
  • +
  • ✓ 获得专业的医疗监测和照护
  • +
  • ✓ 帮助更多患者获得更好的治疗
  • +
+

基本流程

+
+
+
1
+

知情同意

+
+
+
+
2
+

筛选入组

+
+
+
+
3
+

试验治疗

+
+
+
+
4
+

随访监测

+
+
+
+
+
+
+ +
+
+
+

受试者权益

+
+
+
📋
+

知情同意权

+

+ 您有权充分了解试验的目的、方法、可能的风险和获益, + 在充分理解的基础上自愿决定是否参与试验。 +

+
+
+
🛡️
+

安全保障权

+

+ 您有权获得安全的医疗照护,试验过程中发生的任何不良事件 + 都会得到及时、专业的处理和救治。 +

+
+
+
💰
+

损害救济权

+

+ 如果因参与试验而受到损害,您有权获得及时、充分的医疗救治 + 和经济补偿,无需垫付医疗费用。 +

+
+
+
🔄
+

退出权

+

+ 您有权在任何时候退出试验,无需说明理由, + 且不会影响您获得其他医疗服务的权利。 +

+
+
+
+
+
+ +
+
+
+

损害救济

+
+

赔偿/补偿原则

+
+
+

最大范围医疗报销原则

+

+ 覆盖范围:与试验相关的一切风险
+ 报销比例:必要且合理的医疗支出100%报销
+ 特点:高频、时效、便捷、最好无垫付 +

+
+
+

身故、伤残赔偿金

+

+ 对于因试验导致的严重损害,提供身故和伤残赔偿金保障(低频事件) +

+
+
+

无过错责任补偿金

+

+ 即使无过错,只要与试验相关,也会提供补偿(低频事件) +

+
+
+

精算损失赔偿金

+

+ 对于其他损失,根据精算原则提供合理赔偿(低频事件) +

+
+
+

救济流程

+
+
+
1
+

出险通知

+

医院第一时间通知RMO服务方

+
+
+
2
+

快速响应

+

24小时内与医院、受试者沟通

+
+
+
3
+

医疗救治

+

及时安排并承诺受试者救治

+
+
+
4
+

费用结算

+

与医院直接结算,无需受试者垫付

+
+
+

联系方式

+
+

紧急热线:400-XXX-XXXX(24小时)

+

服务邮箱:service@rmo.com

+

服务时间:全天候24小时服务

+
+
+
+
+
+
+
+
+ ) +} + +export default Participant + diff --git a/src/pages/PostMarket.css b/src/pages/PostMarket.css new file mode 100644 index 0000000..52a5d1c --- /dev/null +++ b/src/pages/PostMarket.css @@ -0,0 +1,40 @@ +.post-market .building-notice { + text-align: center; + font-size: 1.25rem; + color: var(--text-secondary, #666); + margin: 2rem 0; +} + +.post-market .value-list { + list-style: none; + padding: 0; + margin: 16px 0; +} + +.post-market .value-list li { + padding: 8px 0; + padding-left: 20px; + position: relative; +} + +.post-market .value-list li::before { + content: '✓'; + position: absolute; + left: 0; + color: var(--brand-primary); + font-weight: 600; +} + +.post-market .contact-note { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid var(--border-color); +} + +.post-market .contact-note a { + color: var(--brand-primary); +} + +.post-market .contact-note a:hover { + text-decoration: underline; +} diff --git a/src/pages/PostMarket.tsx b/src/pages/PostMarket.tsx new file mode 100644 index 0000000..ebe479e --- /dev/null +++ b/src/pages/PostMarket.tsx @@ -0,0 +1,28 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './PostMarket.css' + +function PostMarket() { + return ( + +
+ +
+
+
+
+

建设中

+
+
+
+
+
+
+ ) +} + +export default PostMarket diff --git a/src/pages/ResourceCenter.css b/src/pages/ResourceCenter.css new file mode 100644 index 0000000..77e6abc --- /dev/null +++ b/src/pages/ResourceCenter.css @@ -0,0 +1,191 @@ +/* 资源中心页面容器(与导航「资源中心」一致) */ +.resource-center { + /* 页面级样式可在此扩展 */ +} + +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.guide-grid, +.training-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin: 30px 0; +} + +.guide-item, +.training-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.guide-item h3, +.training-item h3 { + font-size: 18px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.guide-item p, +.training-item p { + font-size: 14px; + color: var(--text-light); + margin: 0; +} + +.case-list, +.doc-list, +.course-list { + margin: 30px 0; +} + +.case-item, +.doc-item, +.course-item { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + margin-bottom: 15px; + border-left: 4px solid var(--primary-color); +} + +.case-item h3, +.doc-item h3, +.course-item h3 { + font-size: 18px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.case-item p, +.doc-item p, +.course-item p { + font-size: 14px; + color: var(--text-color); + margin: 0; +} + +.faq-list { + margin: 30px 0; +} + +.faq-item { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + margin-bottom: 15px; +} + +.faq-item h3 { + font-size: 16px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.faq-item p { + font-size: 14px; + color: var(--text-color); + line-height: 1.6; + margin: 0; +} + +.nav-section { + background: var(--bg-color); +} + +.nav-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin-top: 40px; +} + +.nav-card { + background: var(--white); + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 40px 30px; + text-align: center; + transition: all 0.3s ease; + display: block; +} + +.nav-card:hover { + border-color: rgba(var(--brand-primary-rgb), 0.3); + box-shadow: var(--shadow-hover); + filter: brightness(1.02); +} + +.nav-icon { + font-size: 64px; + margin-bottom: 20px; +} + +.nav-card h3 { + font-size: 22px; + margin-bottom: 15px; + color: var(--text-color); +} + +.nav-card p { + font-size: 14px; + color: var(--text-light); +} + +.resource-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; + margin-top: 30px; +} + +.resource-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.resource-item h3 { + font-size: 20px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.resource-item ul { + list-style: none; + padding: 0; +} + +.resource-item li { + padding: 8px 0; + font-size: 15px; + color: var(--text-color); +} + +@media (max-width: 768px) { + .guide-grid, + .training-grid { + grid-template-columns: 1fr; + } + + .nav-grid, + .resource-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/ResourceCenter.tsx b/src/pages/ResourceCenter.tsx new file mode 100644 index 0000000..3584d70 --- /dev/null +++ b/src/pages/ResourceCenter.tsx @@ -0,0 +1,203 @@ +import { useLocation } from 'react-router-dom' +import ResourceCenterOverview from './ResourceCenterOverview' +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './ResourceCenter.css' + +// 法律法规页面 +function LawsRegulations() { + return ( + +
+ +
+
+
+
+

法律法规

+

与临床试验风险主体、受试者保护及保险保证相关的法律法规与规范性文件。

+
+
+

📜 GCP 与受试者保护

+

《药物临床试验质量管理规范》等与临床试验及受试者保护相关的法规要求。

+
+
+

📜 申办者责任

+

申办者应向研究者和临床试验机构提供法律上、经济上的保险或保证,与风险性质和程度相适应。

+
+
+

📜 药品管理相关

+

《药品管理法》等对持有人责任与风险管理能力的要求。

+
+
+

📜 保险与保证

+

与保险条款、理赔及保证方式相关的法规与行业规范。

+
+
+
+
+
+
+
+
+ ) +} + +// 实践指南页面 +function PracticeGuide() { + return ( + +
+ +
+
+
+
+

操作指南

+

提供详细的操作指南文档,帮助您更好地使用RMO服务。

+ +
+
+

📋 理赔流程指南

+

详细的理赔申请流程说明,包括所需资料、处理时效等。

+
+
+

📝 项目启动指南

+

项目启动时的保险投保和基金设立流程指南。

+
+
+

🔍 风险评估指南

+

如何识别和评估临床试验风险的方法和工具。

+
+
+

📊 报告提交指南

+

各类报告和文档的提交要求和格式规范。

+
+
+ +

最佳实践案例

+
+
+

案例一:SUSAR理赔处理

+

详细介绍SUSAR案件的理赔处理流程和注意事项。

+
+
+

案例二:风险减量服务应用

+

通过风险减量服务成功降低试验风险的实践案例。

+
+
+

案例三:外溢风险管理

+

外溢风险管理服务在复杂案件中的应用实践。

+
+
+
+
+
+
+
+
+ ) +} + +// 培训材料页面 +function Training() { + return ( + +
+ +
+
+
+
+

培训视频

+
+
+

🎥 RMO模式介绍

+

全面介绍RMO模式的核心概念和服务内容。

+
+
+

🎥 理赔流程培训

+

详细的理赔申请流程和操作培训视频。

+
+
+

🎥 CRC培训课程

+

针对临床研究协调员的专业培训课程。

+
+
+

🎥 风险管理培训

+

临床试验风险识别和管理方法培训。

+
+
+ +

培训文档

+
+
+

📄 服务手册

+

完整的RMO服务手册,包含所有服务内容和使用说明。

+
+
+

📄 操作指南

+

详细的操作指南文档,涵盖各项服务的操作流程。

+
+
+

📄 培训PPT

+

各类培训课程的PPT资料,可供下载学习。

+
+
+ +

培训课程

+
+
+

📚 基础课程

+

RMO模式基础知识和入门课程。

+
+
+

📚 进阶课程

+

深入的风险管理和理赔处理课程。

+
+
+

📚 专业认证

+

专业认证课程和考试安排。

+
+
+
+
+
+
+
+
+ ) +} + +/** 资源中心页:实践指南、培训材料、常见问题入口与子页 */ +function ResourceCenter() { + const location = useLocation() + + // 如果是主页路径,显示概览 + if (location.pathname === '/system-management' || location.pathname === '/system-management/') { + return + } + + // 根据路径显示不同内容 + if (location.pathname.includes('/laws')) { + return + } else if (location.pathname.includes('/practice-guide')) { + return + } else if (location.pathname.includes('/training')) { + return + } + + // 默认显示概览 + return +} + +export default ResourceCenter diff --git a/src/pages/ResourceCenterOverview.tsx b/src/pages/ResourceCenterOverview.tsx new file mode 100644 index 0000000..c1cf775 --- /dev/null +++ b/src/pages/ResourceCenterOverview.tsx @@ -0,0 +1,107 @@ +import { Link } from 'react-router-dom' +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './ResourceCenter.css' + +/** 资源中心概览页:实践指南、培训材料、常见问题入口 */ +function ResourceCenterOverview() { + return ( + +
+ +
+
+
+
+

关于资源中心

+

+ 资源中心为您提供全面的实践指南、培训材料和常见问题等资源,帮助您更好地理解和使用RMO服务。 + 我们致力于通过系统化的知识管理和培训支持,提升各方在临床试验与上市后风险管理方面的专业能力。 +

+

+ 无论您是申办者、研究中心还是服务方,都可以在这里找到适合您的实践指南、培训资源和常见问题解答, + 帮助您更好地履行风险管理职责,确保临床试验与药品全生命周期的安全和质量。 +

+
+
+
+ +
+
+

资源中心

+
+ +
📜
+

法律法规

+

临床试验与风险管理相关法律法规

+ + +
📋
+

实践指南

+

操作指南文档与最佳实践案例

+ + +
🎓
+

培训材料

+

培训视频、文档和课程资源

+ + +
+

常见问题

+

保险、保证与流程相关FAQ

+ +
+
+
+ +
+
+
+

资源内容

+
+
+

📋 实践指南

+
    +
  • • 理赔流程指南
  • +
  • • 项目启动指南
  • +
  • • 风险评估指南
  • +
  • • 报告提交指南
  • +
  • • 最佳实践案例
  • +
+
+
+

🎓 培训材料

+
    +
  • • RMO模式介绍视频
  • +
  • • 理赔流程培训
  • +
  • • CRC培训课程
  • +
  • • 风险管理培训
  • +
  • • 培训文档和PPT
  • +
+
+
+

❓ 常见问题

+
    +
  • • 保险相关问题
  • +
  • • 保证方案问题
  • +
  • • 理赔与流程FAQ
  • +
+

+ 查看常见问题 → +

+
+
+
+
+
+
+
+
+ ) +} + +export default ResourceCenterOverview diff --git a/src/pages/RiskData.css b/src/pages/RiskData.css new file mode 100644 index 0000000..d68d605 --- /dev/null +++ b/src/pages/RiskData.css @@ -0,0 +1,9 @@ +.risk-data-page { + min-height: 100%; +} + +.risk-data-page .building-notice { + text-align: center; + padding: var(--padding-xl, 24px); + color: var(--text-light, #666); +} diff --git a/src/pages/RiskDutiesOverview.css b/src/pages/RiskDutiesOverview.css new file mode 100644 index 0000000..4f8b100 --- /dev/null +++ b/src/pages/RiskDutiesOverview.css @@ -0,0 +1,95 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.nav-section { + background: var(--bg-color); +} + +.nav-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin-top: 40px; +} + +.nav-card { + background: var(--white); + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 40px 30px; + text-align: center; + transition: all 0.3s ease; + display: block; +} + +.nav-card:hover { + border-color: rgba(var(--brand-primary-rgb), 0.3); + box-shadow: var(--shadow-hover); + filter: brightness(1.02); +} + +.nav-icon { + font-size: 64px; + margin-bottom: 20px; +} + +.nav-card h3 { + font-size: 22px; + margin-bottom: 15px; + color: var(--text-color); +} + +.nav-card p { + font-size: 14px; + color: var(--text-light); +} + +.responsibility-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; + margin-top: 30px; +} + +.responsibility-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.responsibility-item h3 { + font-size: 20px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.responsibility-item ul { + list-style: none; + padding: 0; +} + +.responsibility-item li { + padding: 8px 0; + font-size: 15px; + color: var(--text-color); +} + +@media (max-width: 768px) { + .nav-grid, + .responsibility-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/RiskDutiesOverview.tsx b/src/pages/RiskDutiesOverview.tsx new file mode 100644 index 0000000..28d3b51 --- /dev/null +++ b/src/pages/RiskDutiesOverview.tsx @@ -0,0 +1,125 @@ +import { Link } from 'react-router-dom' +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './RiskDutiesOverview.css' + +/** 风险职责总览页:各方关注 / 各方职责入口(申办者、持有人、受试者、研究中心、CXO) */ +function RiskDutiesOverview() { + return ( + +
+ +
+
+
+
+

关于风险职责

+

+ 在临床试验中,不同角色承担着不同的职责和关注点。RMO为各方提供针对性的风险管理支持, + 确保临床试验过程中受试者的安全,同时帮助各方更好地履行其职责。 +

+

+ 我们为申办者、研究中心、CXO(CRO、CDMO、SMO)等各方提供专业的风险管理服务, + 帮助各方识别风险、评估风险、管理风险,确保临床试验的顺利进行。 +

+
+
+
+ +
+
+

各方职责

+
+ +
💼
+

申办者职责

+

风险管理体系

+ + +
📋
+

持有人职责

+

负责上市后药物安全

+ + +
👤
+

受试者专区

+

临床试验介绍、权益保障、损害救济

+ + +
🏥
+

研究中心

+

机构、研究者、伦理委员会支持

+ + +
🤝
+

CXO职责

+

CRO、CDMO、SMO支持服务

+ +
+
+
+ +
+
+
+

各方职责概述

+
+
+

申办者

+
    +
  • • 承担受试者保护的主要责任
  • +
  • • 提供法律上、经济上的保险或保证
  • +
  • • 建立完善的风险管理体系
  • +
  • • 确保临床试验的质量和安全
  • +
+
+
+

持有人

+
    +
  • • 建立药物警戒体系,开展上市后安全性监测
  • +
  • • 收集、评价和报告药品不良反应
  • +
  • • 开展药品上市后研究,持续评估风险与获益
  • +
  • • 制定并实施药品风险管理计划
  • +
+
+
+

研究中心

+
    +
  • • 协助风险管理,保障受试者安全
  • +
  • • 研究者负责临床试验的执行
  • +
  • • 伦理委员会负责伦理审查
  • +
  • • 及时报告不良事件
  • +
+
+
+

受试者

+
    +
  • • 了解临床试验基本知识
  • +
  • • 知情同意与权益保障
  • +
  • • 损害救济与补偿机制
  • +
+
+
+

CXO服务方

+
    +
  • • CRO:协助申办者进行临床试验
  • +
  • • CDMO:提供药物开发和生产服务
  • +
  • • SMO:提供现场管理服务
  • +
  • • 协助风险管理流程的执行
  • +
+
+
+
+
+
+
+
+
+ ) +} + +export default RiskDutiesOverview diff --git a/src/pages/RmoMode.css b/src/pages/RmoMode.css new file mode 100644 index 0000000..d12d6c5 --- /dev/null +++ b/src/pages/RmoMode.css @@ -0,0 +1,1341 @@ +/* 规范:页面头部使用科技蓝渐变 */ +.hero-section { + background: linear-gradient( + to right, + rgba(224, 242, 254, 0.98) 0%, + rgba(186, 230, 253, 0.95) 50%, + rgba(125, 211, 252, 0.9) 100% + ); + color: var(--brand-text-default); + padding: 60px 0 40px; + text-align: center; + border: 1px solid rgba(14, 165, 233, 0.25); + box-shadow: 0 2px 8px rgba(14, 165, 233, 0.12); +} + +.hero-section .section-title { + color: var(--brand-text-default); +} + +.hero-section .section-subtitle { + color: var(--text-light); +} + +/* ========== 为什么需要风险管理(图示化) ========== */ +.section-why-rm .section-title { + margin-bottom: 8px; +} + +.section-why-rm .section-subtitle { + margin-bottom: 28px; + max-width: 720px; + margin-left: auto; + margin-right: auto; +} + +.why-rm-diagram { + position: relative; + margin-bottom: 28px; +} + +.why-rm-center { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 160px; + height: 160px; + margin: 0 auto 24px; + padding: 20px; + background: linear-gradient(135deg, rgba(14, 165, 233, 0.12) 0%, rgba(56, 189, 248, 0.08) 100%); + border: 2px solid rgba(14, 165, 233, 0.35); + border-radius: 50%; + box-shadow: 0 4px 20px rgba(14, 165, 233, 0.15); +} + +.why-rm-center-icon { + font-size: 40px; + margin-bottom: 8px; +} + +.why-rm-center-text { + font-size: 15px; + font-weight: 600; + color: var(--brand-primary); + text-align: center; + line-height: 1.3; +} + +.why-rm-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; + max-width: 1000px; + margin: 0 auto; +} + +.why-rm-card { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 20px 16px; + background: var(--white); + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease; +} + +.why-rm-card:hover { + border-color: rgba(14, 165, 233, 0.3); + box-shadow: 0 6px 24px rgba(14, 165, 233, 0.12); +} + +.why-rm-card-icon { + font-size: 36px; + margin-bottom: 12px; +} + +.why-rm-card h4 { + font-size: 15px; + font-weight: 600; + margin: 0 0 8px 0; + color: var(--brand-primary); + line-height: 1.3; +} + +.why-rm-card p { + font-size: 13px; + line-height: 1.5; + color: var(--text-color); + margin: 0; +} + +.why-rm-quote { + max-width: 720px; + margin: 0 auto; + padding: 20px 24px !important; + border-left: 4px solid var(--brand-primary); + background: linear-gradient(to right, rgba(14, 165, 233, 0.06) 0%, transparent 100%); +} + +.why-rm-quote-text { + font-size: 15px; + line-height: 1.7; + color: var(--text-color); + margin: 0; + font-style: italic; +} + +.why-rm-images { + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; + margin-top: 20px; +} + +.why-rm-image-wrap { + max-width: 900px; + width: 100%; + margin: 0 auto; + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--border-color); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +.why-rm-image { + display: block; + width: 100%; + height: auto; + vertical-align: middle; +} + +@media (max-width: 768px) { + .why-rm-center { + width: 130px; + height: 130px; + font-size: 14px; + } + + .why-rm-center-icon { + font-size: 32px; + } + + .why-rm-cards { + grid-template-columns: 1fr 1fr; + } +} + +/* ========== 临床试验风险管理首页 ========== */ +.section-block-title { + font-size: 22px; + font-weight: 600; + margin: 0 0 12px 0; + color: var(--brand-text-default); +} + +.section-block-desc { + font-size: 16px; + line-height: 1.7; + color: var(--text-color); + margin: 0 0 20px 0; +} + +.clinical-trial-purpose-card .purpose-list { + list-style: none; + padding: 0; + margin: 0; +} + +.clinical-trial-purpose-card .purpose-list li { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px 0; + font-size: 15px; + line-height: 1.6; + color: var(--text-color); + border-bottom: 1px solid var(--border-color); +} + +.clinical-trial-purpose-card .purpose-list li:last-child { + border-bottom: none; +} + +.purpose-icon { + flex-shrink: 0; + font-size: 24px; +} + +.pathway-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: var(--padding-md); + margin-top: 20px; +} + +.pathway-card { + display: flex; + flex-direction: column; + padding: 24px; + background: var(--white); + border-radius: 8px; + border: 1px solid var(--border-color); + text-decoration: none; + color: inherit; + transition: box-shadow 0.2s ease, filter 0.2s ease, border-color 0.2s ease; +} + +.pathway-card:hover { + border-color: rgba(var(--brand-primary-rgb), 0.3); + box-shadow: var(--shadow-hover); + filter: brightness(1.02); +} + +.pathway-icon { + font-size: 40px; + margin-bottom: 12px; +} + +.pathway-card h3 { + font-size: 20px; + font-weight: 600; + margin: 0 0 10px 0; + color: var(--brand-primary); +} + +.pathway-card p { + font-size: 14px; + line-height: 1.6; + color: var(--text-color); + margin: 0 0 16px 0; + flex: 1; +} + +.pathway-link { + font-size: 14px; + color: var(--brand-primary); + font-weight: 500; +} + +.pathway-card:hover .pathway-link { + text-decoration: underline; +} + +.contact-note { + margin-top: 12px; + font-size: 14px; + color: var(--text-light); +} + +/* ========== 从安全数据到患者救治的闭环(左右结构) ========== */ +.section-closed-loop .section-title { + margin-bottom: 24px; +} + +.closed-loop-layout { + display: flex; + flex-direction: row; + align-items: stretch; + gap: 0; + max-width: 1100px; + margin: 0 auto; +} + +.closed-loop-left { + flex: 0 1 48%; + min-width: 0; + display: flex; + flex-direction: column; +} + +.closed-loop-arrow { + flex: 0 0 56px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + color: var(--brand-primary); + padding: 0 8px; +} + +.closed-loop-arrow-icon { + font-size: 28px; + line-height: 1; + opacity: 0.9; +} + +.closed-loop-arrow-label { + font-size: 11px; + color: var(--text-light); + white-space: nowrap; +} + +.closed-loop-right { + flex: 0 1 48%; + min-width: 0; + display: flex; + flex-direction: column; +} + +.closed-loop-subtitle { + font-size: 16px; + font-weight: 600; + margin: 0 0 12px 0; + color: var(--brand-primary); +} + +.closed-loop-image-wrap { + flex: 1; + min-height: 0; + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--border-color); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +.closed-loop-image { + display: block; + width: 100%; + height: auto; + vertical-align: middle; +} + +.closed-loop-measures { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.closed-loop-measure-card { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 12px 14px; + background: var(--white); + border-radius: 8px; + border: 1px solid var(--border-color); + text-decoration: none; + color: inherit; + transition: box-shadow 0.2s ease, border-color 0.2s ease; +} + +.closed-loop-measure-card:hover { + border-color: rgba(var(--brand-primary-rgb), 0.35); + box-shadow: 0 2px 12px rgba(14, 165, 233, 0.15); +} + +.closed-loop-measure-icon { + font-size: 22px; + margin-bottom: 6px; + line-height: 1; +} + +.closed-loop-measure-card h4 { + font-size: 15px; + font-weight: 600; + margin: 0 0 4px 0; + color: var(--brand-primary); +} + +.closed-loop-measure-card p { + font-size: 12px; + line-height: 1.45; + color: var(--text-color); + margin: 0; + flex: 1; + min-height: 0; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +@media (max-width: 768px) { + .closed-loop-layout { + flex-direction: column; + gap: 20px; + } + + .closed-loop-left, + .closed-loop-right { + flex: 1 1 auto; + } + + .closed-loop-arrow { + flex: none; + padding: 12px 0; + transform: rotate(90deg); + } + + + .closed-loop-image-wrap { + flex: none; + } + + .closed-loop-measures { + flex: none; + gap: 12px; + } + + .closed-loop-measure-card { + flex: none; + min-height: auto; + } +} + +/* ========== 一键获取全部保司报价 ========== */ +.section-get-quotes .container { + max-width: 900px; +} + +.get-quotes-card { + padding: 24px 28px; + margin-bottom: 20px; + border: 1px solid var(--border-color); + border-radius: 12px; + background: var(--white); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); +} + +.get-quotes-title { + font-size: 20px; + font-weight: 600; + margin: 0 0 12px 0; + color: var(--brand-primary); +} + +.get-quotes-desc { + font-size: 14px; + line-height: 1.65; + color: var(--text-color); + margin: 0 0 20px 0; +} + +.get-quotes-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 16px; +} + +.get-quotes-btn { + padding: 12px 24px; + font-size: 15px; + font-weight: 500; + border-radius: 8px; + cursor: pointer; + border: none; + background: var(--brand-primary); + color: var(--white); + transition: background 0.2s ease, box-shadow 0.2s ease; +} + +.get-quotes-btn:hover { + background: #0c8ac7; + box-shadow: 0 4px 12px rgba(14, 165, 233, 0.35); +} + +/* ========== 保险方案页面 4×3 网格 ========== */ +.section-insurance-grid .container { + max-width: 1100px; +} + +.insurance-page-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: auto auto 1fr; + gap: 16px; + min-height: 420px; +} + +.insurance-grid-cell { + padding: 20px; + background: var(--white); + border-radius: 10px; + border: 1px solid var(--border-color); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); +} + +.insurance-cell-title { + font-size: 17px; + font-weight: 600; + margin: 0 0 12px 0; + color: var(--brand-primary); +} + +.insurance-grid-cell p { + font-size: 14px; + line-height: 1.65; + color: var(--text-color); + margin: 0; +} + +/* 左上 3×1:基础保障 */ +.insurance-cell-basic { + grid-column: 1 / 4; + grid-row: 1; +} + +/* 中部 3×1:全面保障 */ +.insurance-cell-full { + grid-column: 1 / 4; + grid-row: 2; +} + +/* 右侧 1×2:保险团体标准核心内容 */ +.insurance-cell-standard { + grid-column: 4; + grid-row: 1 / 3; +} + +.insurance-standard-list { + list-style: none; + padding: 0; + margin: 0; +} + +.insurance-standard-list li { + position: relative; + padding: 8px 0 8px 18px; + font-size: 14px; + line-height: 1.5; + color: var(--text-color); +} + +.insurance-standard-list li::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--brand-primary); +} + +/* 底部 4×1:保险服务 */ +.insurance-cell-services { + grid-column: 1 / -1; + grid-row: 3; +} + +.insurance-cell-services .service-item { + margin-bottom: 0; +} + +/* ========== 服务供应商 ========== */ +.section-service-providers { + margin-top: 32px; +} + +.section-service-providers .section-title { + font-size: 22px; + font-weight: 600; + margin: 0 0 12px 0; + color: var(--brand-text-default); +} + +.section-service-providers .section-desc { + font-size: 15px; + color: var(--text-color); + margin: 0 0 20px 0; + line-height: 1.6; +} + +.service-provider-logos { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 24px 32px; + margin-top: 16px; +} + +.service-provider-logo-item { + display: flex; + align-items: center; + justify-content: center; + width: 140px; + height: 80px; + padding: 12px 16px; + background: var(--white); + border-radius: 8px; + border: 1px solid var(--border-color); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + transition: box-shadow 0.2s ease; +} + +.service-provider-logo-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.service-provider-logo-img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + display: block; +} + +@media (max-width: 768px) { + .insurance-page-grid { + grid-template-columns: 1fr; + grid-template-rows: auto; + min-height: 0; + } + + .insurance-cell-basic, + .insurance-cell-full, + .insurance-cell-standard, + .insurance-cell-services { + grid-column: 1; + grid-row: auto; + } +} + +/* 保证方案页:保证基金的基本逻辑图 */ +.guarantee-logic-figure { + margin: 32px 0 40px; +} + +.guarantee-logic-figure h3 { + font-size: 20px; + margin: 0 0 16px 0; + color: var(--brand-primary); +} + +.guarantee-logic-img { + display: block; + max-width: 100%; + width: 100%; + height: auto; + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +/* 保证基金管理形式比较表 */ +.guarantee-compare-section { + margin: 40px 0 40px; +} + +.guarantee-compare-section h3 { + font-size: 20px; + margin: 0 0 16px 0; + color: var(--brand-primary); +} + +.guarantee-compare-table-wrap { + overflow-x: auto; + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); +} + +.guarantee-compare-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; + background: var(--white); +} + +.guarantee-compare-table thead { + background: linear-gradient(to right, rgba(14, 165, 233, 0.12) 0%, rgba(56, 189, 248, 0.08) 100%); + border-bottom: 2px solid rgba(14, 165, 233, 0.35); +} + +.guarantee-compare-table th { + padding: 14px 16px; + text-align: left; + font-weight: 600; + color: var(--brand-primary); + white-space: nowrap; +} + +.guarantee-compare-table th:first-child { + min-width: 100px; +} + +.guarantee-compare-table td { + padding: 14px 16px; + line-height: 1.6; + color: var(--text-color); + border-bottom: 1px solid var(--border-color); + vertical-align: top; +} + +.guarantee-compare-table tbody tr:last-child td { + border-bottom: none; +} + +.guarantee-compare-table tbody tr:hover { + background: rgba(14, 165, 233, 0.04); +} + +.guarantee-compare-table .compare-row-label { + font-weight: 600; + color: var(--brand-text-default); + min-width: 100px; +} + +/* 保险+保证方案页:比较区块与配置步骤 */ +.insurance-guarantee-compare { + margin-bottom: 40px; +} + +.config-section { + margin-top: 32px; +} + +.config-content { + padding: 0 0 10px 0; +} + +.config-steps { + list-style: none; + padding: 0; + margin: 0; + counter-reset: config-step; +} + +.config-steps li { + position: relative; + padding: 20px 0 20px 52px; + border-bottom: 1px solid var(--border-color); + counter-increment: config-step; +} + +.config-steps li:last-child { + border-bottom: none; +} + +.config-steps li::before { + position: absolute; + left: 0; + top: 22px; + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; + font-size: 14px; + font-weight: 700; + color: var(--white); + background: var(--brand-primary); + border-radius: 50%; + content: counter(config-step); +} + +.config-steps li strong { + display: block; + font-size: 17px; + color: var(--brand-text-default); + margin-bottom: 8px; +} + +.config-steps li p { + font-size: 15px; + line-height: 1.7; + color: var(--text-color); + margin: 0; +} + +@media (max-width: 768px) { + .guarantee-compare-table-wrap { + margin: 0 -16px; + border-radius: 0; + border-left: none; + border-right: none; + } + + .guarantee-compare-table th, + .guarantee-compare-table td { + padding: 12px 14px; + font-size: 13px; + } + + .guarantee-compare-table th { + white-space: normal; + } +} + +/* 相关服务:风险减量 / 外溢风险管理 分栏并列 */ +.related-service-columns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 28px 32px; + margin-top: 8px; +} + +.related-service-column { + padding: 20px; + background: linear-gradient(to bottom, rgba(14, 165, 233, 0.04) 0%, transparent 100%); + border-radius: 12px; + border: 1px solid var(--border-color); +} + +.related-service-column h3 { + margin-top: 0; + margin-bottom: 16px; + padding-bottom: 10px; + border-bottom: 2px solid rgba(14, 165, 233, 0.25); + color: var(--brand-primary); +} + +.related-service-column .service-list { + margin: 12px 0 16px; +} + +.related-service-column .service-list li { + padding: 6px 0; +} + +.related-service-column p { + margin-bottom: 12px; +} + +.related-service-column p:last-child { + margin-bottom: 0; +} + +@media (max-width: 768px) { + .related-service-columns { + grid-template-columns: 1fr; + gap: 24px; + } +} + +.service-item { + margin-bottom: 40px; +} + +.service-header { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid var(--border-color); +} + +.service-icon { + font-size: 48px; +} + +.service-header h2 { + font-size: 28px; + color: var(--primary-color); + margin: 0; +} + +.service-content h3 { + font-size: 20px; + margin: 25px 0 15px; + color: var(--text-color); +} + +.service-content p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 15px; +} + +.service-list { + list-style: none; + padding: 0; + margin: 15px 0; +} + +.service-list li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.insurance-services { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin: 20px 0; +} + +.insurance-item { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.insurance-item h4 { + font-size: 18px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.insurance-item p { + font-size: 14px; + color: var(--text-light); + margin: 0; +} + +.guarantee-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; + margin: 30px 0; +} + +.guarantee-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.guarantee-item h4 { + font-size: 20px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.guarantee-item ul { + list-style: none; + padding: 0; +} + +.guarantee-item li { + padding: 8px 0; + font-size: 15px; + color: var(--text-color); +} + +.process-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 30px; + margin: 30px 0; +} + +.process-step { + text-align: center; +} + +.step-number { + width: 50px; + height: 50px; + background: var(--primary-color); + color: var(--white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + font-weight: bold; + margin: 0 auto 15px; +} + +.process-step h4 { + font-size: 18px; + margin-bottom: 10px; + color: var(--text-color); +} + +.process-step p { + font-size: 14px; + color: var(--text-light); +} + +.intro-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; + margin-top: 30px; +} + +.intro-grid .card h3 { + font-size: 24px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.value-list { + list-style: none; + padding: 0; +} + +.value-list li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.value-list a { + color: var(--primary-color); + text-decoration: none; +} + +.value-list a:hover { + text-decoration: underline; +} + +.nav-section { + background: var(--bg-color); +} + +.nav-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin-top: 40px; +} + +.nav-card { + background: var(--white); + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 40px 30px; + text-align: center; + transition: all 0.3s ease; + display: block; +} + +.nav-card:hover { + border-color: rgba(var(--brand-primary-rgb), 0.3); + box-shadow: var(--shadow-hover); + filter: brightness(1.02); +} + +.nav-icon { + font-size: 64px; + margin-bottom: 20px; +} + +.nav-card h3 { + font-size: 22px; + margin-bottom: 15px; + color: var(--text-color); +} + +.nav-card p { + font-size: 14px; + color: var(--text-light); +} + +/* RMO轮播容器样式 */ +.rmo-carousel-container { + position: relative; + width: 100%; + margin-bottom: 30px; +} + +.rmo-carousel-wrapper { + position: relative; + width: 100%; + overflow: hidden; + border-radius: 12px; + margin-bottom: 20px; +} + +.carousel-controls { + display: flex; + align-items: center; + justify-content: center; + gap: 20px; + margin-top: 15px; +} + +.rmo-carousel-track { + display: flex; + transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); + will-change: transform; +} + +.rmo-carousel-slide { + min-width: 100%; + flex-shrink: 0; + padding: 0 10px; + box-sizing: border-box; +} + +.rmo-intro-card, +.rmo-feature-card { + background: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%); + border-left: 4px solid var(--primary-color); + display: flex; + flex-direction: column; + height: 100%; + min-height: 200px; + margin: 0 auto; + max-width: 800px; +} + +.rmo-title-section { + text-align: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 2px solid var(--border-color); +} + +.rmo-title-section h2 { + font-size: 32px; + font-weight: 700; + color: var(--primary-color); + margin-bottom: 10px; +} + +.rmo-subtitle { + font-size: 15px; + color: var(--text-light); + font-style: italic; + margin: 0; +} + +.feature-title { + font-size: 32px; + font-weight: 700; + color: #ff6b6b; + margin-bottom: 10px; +} + +.rmo-mission { + display: flex; + flex-direction: column; + gap: 12px; + flex: 1; +} + +.mission-item { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px 15px; + background: var(--white); + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + transition: transform 0.3s ease, box-shadow 0.3s ease; + flex: 1; +} + +.mission-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + filter: brightness(1.03); +} + +.mission-icon { + font-size: 24px; + flex-shrink: 0; + line-height: 1; +} + +.mission-content { + flex: 1; +} + +.mission-content h3 { + font-size: 15px; + color: var(--text-color); + margin: 0; + line-height: 1.5; + font-weight: 500; +} + +.rmo-feature-card { + border-left: 4px solid #ff6b6b; +} + +@media (max-width: 768px) { + .service-header { + flex-direction: column; + text-align: center; + } + + .insurance-services, + .guarantee-grid { + grid-template-columns: 1fr; + } + + .process-steps { + grid-template-columns: 1fr; + } + + .intro-grid, + .nav-grid { + grid-template-columns: 1fr; + } +} + +/* 轮播导航按钮 */ +.carousel-btn { + position: relative; + background: var(--white); + border: none; + border-radius: 8px; + width: 48px; + height: 48px; + font-size: 24px; + font-weight: bold; + color: var(--primary-color); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + line-height: 1; +} + +.carousel-btn:hover { + background: var(--primary-color); + color: var(--white); + box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4); + transform: scale(1.1); +} + +.carousel-btn:active { + transform: scale(0.95); +} + +.carousel-btn-prev::before { + content: '←'; + font-size: 20px; +} + +.carousel-btn-next::before { + content: '→'; + font-size: 20px; +} + +/* 轮播指示器 */ +.carousel-indicators { + display: flex; + justify-content: center; + gap: 10px; + z-index: 10; +} + +.carousel-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + border: 2px solid var(--primary-color); + background: transparent; + cursor: pointer; + transition: all 0.3s ease; + padding: 0; +} + +.carousel-indicator:hover { + background: rgba(24, 144, 255, 0.3); +} + +.carousel-indicator.active { + background: var(--primary-color); + width: 32px; + border-radius: 6px; +} + +@media (max-width: 768px) { + .rmo-carousel-slide { + padding: 0 5px; + } + + .carousel-controls { + gap: 15px; + margin-top: 12px; + } + + .carousel-btn { + width: 40px; + height: 40px; + } + + .carousel-btn-prev::before, + .carousel-btn-next::before { + font-size: 18px; + } + + .carousel-btn-prev { + left: 5px; + } + + .carousel-btn-next { + right: 5px; + } + + .rmo-intro-card, + .rmo-feature-card { + min-height: 180px; + } + + .rmo-title-section { + margin-bottom: 10px; + padding-bottom: 8px; + } + + .rmo-mission { + gap: 10px; + } + + .mission-item { + padding: 10px 12px; + gap: 10px; + } + + .rmo-title-section h2, + .feature-title { + font-size: 24px; + } + + .mission-content h3 { + font-size: 14px; + } +} diff --git a/src/pages/RmoMode.tsx b/src/pages/RmoMode.tsx new file mode 100644 index 0000000..9ff79de --- /dev/null +++ b/src/pages/RmoMode.tsx @@ -0,0 +1,384 @@ +import { useLocation, useNavigate } from 'react-router-dom' +import RmoModeOverview from './RmoModeOverview' +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import { useAuth } from '../contexts/AuthContext' +import { useQuoteModal } from '../contexts/QuoteModalContext' +import './RmoMode.css' + +// 保险方案页面(4 列 × 3 行网格) +function Insurance() { + const { isAuthenticated } = useAuth() + const navigate = useNavigate() + const { openQuoteModal } = useQuoteModal() + + const handleGetAllQuotes = () => { + if (!isAuthenticated) { + if (window.confirm('使用「获取精准报价」需要先登录,是否前往登录?')) { + navigate('/login', { state: { from: { pathname: '/rmo-mode/insurance' } } }) + } + return + } + navigate('/dashboard/project-quotes') + } + + return ( + +
+ +
+ {/* 获取报价入口 */} +
+
+
+

获取报价

+

+ 填写项目方案编号、项目标题、申办者、项目分期(可手动填写或上传项目方案由 AI 识别),生成报价后可向各保司获取精准报价。系统将询价邮件发送至保司,保司回复至 rmo@vdano.com,经临研安审核后在报价页面展示。 +

+
+ + {isAuthenticated && ( + + )} +
+
+
+
+ +
+
+
+ {/* 左上 3×1:基础保障 */} +
+

基础保障

+

+ - 希望保险能够承担尽可能多的与患者治疗相关的费用,并协助处理相关事宜。 + - 在满足基础合规的前提下,扩大保障范围、优化理赔流程, + - 准确的保费制定相对困难,已有各类拒赔与纠纷事件发生。 +

+
+ {/* 中部 3×1:全面保障 */} +
+

全面保障

+

+ 希望保险能够承担尽可能多的与患者治疗相关的费用,并协助处理相关事宜。 + 在满足基础合规的前提下,扩大保障范围、优化理赔流程与医疗直付等服务, + 减轻受试者与申办者负担,提升试验可及性与患者安全。 +

+
+ {/* 右侧 1×2:保险团体标准核心内容 */} +
+

保险团体标准核心内容

+
    +
  • 高风险SUSAR与低风险AE应分开定价
  • +
  • 保司/经纪人应提供面向患者的服务
  • +
  • 应提供风险减量相关服务
  • +
  • 需由医学、药物安全专家参与理赔协助
  • +
  • 查看资料原文
  • +
+
+ {/* 底部 4×1:保险服务(保留当前内容) */} +
+
+
+
🛡️
+

保险服务

+

以下服务由具备相关资质的保险、经纪及TPA(第三方服务提供商)提供

+
+
+

服务内容

+
+
+

保险合同审查

+

专业审查保险合同条款,确保保障范围合理

+
+
+

理赔审查

+

协助理赔流程,确保快速高效

+
+
+

保险条款修订

+

根据实际需求修订保险条款

+
+
+

理赔规则制定

+

制定合理的理赔规则和标准

+
+
+

团体标准

+

参与制定行业团体标准

+
+
+
+
+
+
+
+
+ {/* 服务供应商 */} +
+
+

服务供应商

+

以下保司与经纪公司遵循团体标准提供保险服务

+
+ {[ + { name: '太平洋保险', file: '太平洋保险logo.jpeg' }, + { name: '中国大地保险', file: '中国大地保险logo.jpg' }, + { name: '华泰财产', file: '华泰财产.jpg' }, + { name: '亚太财产保险', file: '亚太财产保险logo.webp' }, + { name: '中再', file: '中再logo.png' }, + { name: '平安', file: '平安logo.png' }, + { name: '药盾', file: '药盾logo.webp' }, + { name: 'vDano', file: 'vDano.jpg' }, + ].map(({ name, file }) => ( +
+ {name} +
+ ))} +
+
+
+
+
+
+ ) +} + +// 保证方案页面 +function Guarantee() { + return ( + +
+ +
+
+
+ + +
+

保证基金的基本逻辑

+ 保证基金的基本逻辑:申办者、金融托管账号、医院与患者的关系流程 +
+ +
+

保证基金管理形式比较

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
管理形式合规性便捷性增值服务
研究中心符合GCP及机构内控要求,资金用途与试验场景结合紧密依托院内流程,申办者需对接多家中心,操作分散以试验执行为主,较少提供资金托管、对账等增值服务
金融机构受金融监管,托管资质与资金安全要求高,合规性强需按银行/托管流程开户与划款,手续相对规范但环节较多可提供资金托管、对账、报告等专业服务,部分支持医疗场景定制
代支付公司依业务资质与协议约定,需关注与临床试验、医疗支付的合规衔接支付与结算流程通常较便捷,对接医院与申办者效率高以支付与结算为主,可扩展对账、分账等,增值空间取决于合作深度
+
+
+ +
+
+
🔗
+

相关服务

+
+
+
+
+

风险减量服务

+
    +
  • ✓ 针对临床试验链条的各个环节进行风险点检查
  • +
  • ✓ 人员培训,提升风险管理能力
  • +
  • ✓ 方案完善建议,优化试验设计
  • +
  • ✓ 主动识别和预防潜在风险
  • +
+

+ 通过系统化的风险减量服务,帮助申办者主动识别和降低风险, + 减少不良事件的发生,保障试验顺利进行。 +

+
+
+

外溢风险管理服务

+

针对受试者出险后的全流程管理,包括:

+
    +
  • ✓ 及时联系受试者和医院
  • +
  • ✓ 安抚受试者情绪
  • +
  • ✓ 安排就医和医疗救治
  • +
  • ✓ 沟通诉求和合理预期
  • +
  • ✓ 协调各方关系
  • +
+

+ 正常情况下24小时内与医院、受试者沟通,并通知申办者。 + 如遇特殊情况(受试者非集中、境外、无法联系、不可抗力等),采用应急处理模式。 +

+
+
+
+
+
+
+
+
+
+ ) +} + +// 保险+保证方案页面 +function InsuranceGuarantee() { + return ( + +
+ +
+
+
+ {/* 保险与保证的比较 */} +
+

保险与保证的比较

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
比较维度保险保证
合规性 + 受保险监管约束,需符合《保险法》及银保监要求;临床试验责任险需满足GCP及伦理审查对保障的要求;条款与理赔需与法规、行业团体标准衔接。 + + 需符合GCP及机构内控要求,资金用途与试验场景结合;若由金融机构托管,须满足金融监管与托管资质要求;与试验协议、伦理承诺一致。 +
灵活性 + 保障范围、保额、免赔等可在投保时约定;续保与调整受保单周期限制;产品形态相对标准化,定制多通过附加条款或专属方案实现。 + + 基金规模、使用规则、触发条件可按试验与申办者需求设计;可随试验进展调整拨付节奏与范围;更易与特定中心、特定病种或支付流程结合。 +
关键考虑要素 + 首选用于首要风险(如SUSAR、SAE)及大额责任转移;需关注承保条件、除外责任、理赔流程及争议解决;保费与风险匹配、保险人与经纪人服务能力为关键。 + + 适用于次要风险、AE相关医疗费用及补充保障;需明确托管主体、资金安全、使用规则与监督机制;与保险的边界划分、是否与保险联动需在方案设计时厘清。 +
+
+
+ + {/* 如何配置保险与保证方案 */} +
+
+
⚙️
+

如何配置保险与保证方案

+
+
+
    +
  1. + 明确风险分层 +

    区分首要风险(如严重不良事件、大额责任)与次要风险(如一般AE相关医疗费用、补充保障)。首要风险优先通过保险转移,次要风险可考虑保证基金或与保险组合。

    +
  2. +
  3. + 确定合规与机构要求 +

    对照GCP、伦理批件及机构/中心要求,确认保险类型、最低保额及保证资金的合规性;若有金融机构托管,确认托管资质与资金用途约定。

    +
  4. +
  5. + 设计保险方案 +

    根据试验阶段、受试者规模与风险特征,选择或定制临床试验责任险;明确保障范围、免赔、限额及理赔流程;必要时引入具备医学与药物安全能力的保险人或经纪人。

    +
  6. +
  7. + 设计保证方案 +

    确定保证基金规模、资金来源(如申办者拨付)、托管方(研究中心、金融机构或代支付方)及使用规则;约定触发条件、支付流程与对账监督机制。

    +
  8. +
  9. + 衔接与落地 +

    在协议与知情同意中载明保险与保证的覆盖范围及申办者承诺;建立保险理赔与保证支付的协同流程;定期评估保障充足性并随试验进展做必要调整。

    +
  10. +
+
+
+
+
+
+
+
+ ) +} + +/** RMO 模式页:临床试验保险方案、保证方案、保险保证入口与子页 */ +function RmoMode() { + const location = useLocation() + + // 如果是主页路径,显示概览 + if (location.pathname === '/rmo-mode' || location.pathname === '/rmo-mode/') { + return + } + + // 根据路径显示不同内容(先匹配更具体的 insurance-guarantee,避免被 insurance 命中) + if (location.pathname.includes('/insurance-guarantee')) { + return + } + if (location.pathname.includes('/insurance')) { + return + } + if (location.pathname.includes('/guarantee')) { + return + } + + // 默认显示概览 + return +} + +export default RmoMode diff --git a/src/pages/RmoModeOverview.tsx b/src/pages/RmoModeOverview.tsx new file mode 100644 index 0000000..50d004b --- /dev/null +++ b/src/pages/RmoModeOverview.tsx @@ -0,0 +1,86 @@ +import { Link } from 'react-router-dom' +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './RmoMode.css' + +/** RMO 模式概览页:临床试验保险与保证方案入口 */ +function RmoModeOverview() { + return ( + +
+ +
+ {/* 为什么需要风险管理(图片呈现) */} +
+
+

风险管理目的在于保护患者安全:项目中的患者以及上市后更广泛的患者人群

+
+
+ 遵循方案与良好执行带来短期患者安全与准确数据,共同实现长期患者安全 +
+
+
+
+ + + + {/* 风险管理:从安全数据到患者救治的闭环(左右结构) */} +
+
+

风险管理,从安全数据到患者救治的闭环

+
+ {/* 左侧:药物警戒大数据 */} +
+

药物警戒数据业务逻辑

+
+ 药物警戒:已有安全数据、数据处理与递交、风险监测与总结、风险沟通、上市支持、方案审核 +
+
+ {/* 中间:双向箭头(数据与行动相互关系) */} + + {/* 右侧:风险管理措施 */} +
+

风险管理措施,保障患者及时救治

+
+ + 🛡️ +

保险方案

+

SUSAR、SAE 等首要风险保障;合同审查、理赔、医疗直付

+ + + 💰 +

保证方案

+

专项基金、风险减量、外溢风险管理

+ + + 🔄 +

保险与保证

+

保险与保证结合,形成完整保障体系

+ +
+
+
+
+
+ +
+
+
+ ) +} + +export default RmoModeOverview diff --git a/src/pages/ServiceProvider.css b/src/pages/ServiceProvider.css new file mode 100644 index 0000000..503993c --- /dev/null +++ b/src/pages/ServiceProvider.css @@ -0,0 +1,97 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.responsibility-overview { + list-style: none; + padding-left: 0; + margin: 15px 0 0; +} + +.responsibility-overview li { + padding: 8px 0; + padding-left: 1.5em; + position: relative; + line-height: 1.6; +} + +.responsibility-overview li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--primary-color); +} + +.content-section { + margin: 20px 0; +} + +.content-section h3 { + font-size: 22px; + margin: 30px 0 15px; + color: var(--primary-color); +} + +.content-section p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 20px; +} + +.content-section ul { + list-style: none; + padding-left: 20px; + margin: 15px 0; +} + +.content-section li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.support-features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 25px; + margin: 30px 0; +} + +.feature-item { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.feature-item h4 { + font-size: 18px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.feature-item p { + font-size: 15px; + color: var(--text-color); + line-height: 1.6; + margin: 0; +} + +@media (max-width: 768px) { + .support-features { + grid-template-columns: 1fr; + } +} + diff --git a/src/pages/ServiceProvider.tsx b/src/pages/ServiceProvider.tsx new file mode 100644 index 0000000..c296712 --- /dev/null +++ b/src/pages/ServiceProvider.tsx @@ -0,0 +1,170 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './ServiceProvider.css' + +function ServiceProvider() { + return ( + +
+ +
+
+
+
+

CXO职责概述

+
    +
  • CRO:协助申办者进行临床试验
  • +
  • CDMO:提供药物开发和生产服务
  • +
  • SMO:提供现场管理服务
  • +
  • 协助风险管理流程的执行
  • +
+
+
+
+ +
+
+
+

CRO支持

+
+

+ CRO(Contract Research Organization,合同研究组织)作为临床试验的重要服务提供方, + RMO模式可以为CRO提供全面的风险管理支持,帮助CRO更好地履行合同义务, + 保障临床试验的顺利进行。 +

+
+
+

风险识别与评估支持

+

+ 协助CRO进行项目风险识别和评估,提供专业的风险管理工具和方法, + 帮助CRO更好地识别和管理项目风险。 +

+
+
+

受试者安全保障

+

+ 当受试者出险时,提供快速响应和医疗救治支持, + 通过专项风险管理基金保障医疗费用,减轻CRO的负担。 +

+
+
+

风险减量服务

+

+ 针对临床试验链条的各个环节进行风险点检查, + 提供人员培训和方案完善建议,帮助CRO降低项目风险。 +

+
+
+

外溢风险管理

+

+ 针对受试者出险后的全流程管理,包括联系、安抚、安排就医、 + 沟通诉求等,帮助CRO更好地处理风险事件。 +

+
+
+

合作方式

+
    +
  • ✓ 与申办者一起,共同参与RMO模式的风险管理
  • +
  • ✓ 获得专业的风险管理培训和指导
  • +
  • ✓ 享受快速响应和医疗费用保障服务
  • +
  • ✓ 获得风险减量和外溢风险管理支持
  • +
+
+
+
+
+ +
+
+
+

CDMO支持

+
+

+ CDMO(Contract Development and Manufacturing Organization,合同开发与生产组织) + 虽然主要负责药物的开发和制造,但在临床试验过程中,CDMO也可能面临相关风险。 + RMO模式可以为CDMO提供必要的风险管理支持。 +

+
+
+

产品责任风险保障

+

+ 对于因产品问题导致的临床试验风险,提供相应的保障和支持。 +

+
+
+

供应链风险管理

+

+ 协助CDMO识别和管理供应链相关的风险,确保临床试验的顺利进行。 +

+
+
+

质量风险管理

+

+ 提供质量风险识别和管理支持,帮助CDMO确保产品质量符合临床试验要求。 +

+
+
+
+
+
+
+ +
+
+
+

SMO支持

+
+

+ SMO(Site Management Organization,现场管理组织)负责临床试验现场的协调和管理, + RMO模式可以为SMO提供全面的风险管理支持,帮助SMO更好地履行现场管理职责。 +

+
+
+

现场风险管理

+

+ 协助SMO识别和管理现场风险,提供专业的风险管理工具和方法, + 帮助SMO更好地保障现场安全。 +

+
+
+

受试者安全保障

+

+ 当现场受试者出险时,提供快速响应和医疗救治支持, + 通过专项风险管理基金保障医疗费用。 +

+
+
+

人员培训支持

+

+ 为SMO提供风险管理培训,提升现场管理人员的风险识别和处理能力。 +

+
+
+

应急响应支持

+

+ 提供24小时应急响应支持,当现场发生紧急情况时,快速响应和处理。 +

+
+
+

合作优势

+
    +
  • ✓ 专业的风险管理支持,提升现场管理水平
  • +
  • ✓ 快速响应机制,保障现场安全
  • +
  • ✓ 医疗费用保障,减轻SMO负担
  • +
  • ✓ 风险减量服务,降低现场风险
  • +
+
+
+
+
+
+
+
+ ) +} + +export default ServiceProvider + diff --git a/src/pages/Services.css b/src/pages/Services.css new file mode 100644 index 0000000..c87ff5b --- /dev/null +++ b/src/pages/Services.css @@ -0,0 +1,100 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.service-item { + margin-bottom: 40px; +} + +.service-header { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid var(--border-color); +} + +.service-icon { + font-size: 48px; +} + +.service-header h2 { + font-size: 28px; + color: var(--primary-color); + margin: 0; +} + +.service-content h3 { + font-size: 20px; + margin: 25px 0 15px; + color: var(--text-color); +} + +.service-content p { + font-size: 16px; + line-height: 1.8; + color: var(--text-color); + margin-bottom: 15px; +} + +.service-list { + list-style: none; + padding: 0; + margin: 15px 0; +} + +.service-list li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.insurance-services { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin: 20px 0; +} + +.insurance-item { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.insurance-item h4 { + font-size: 18px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.insurance-item p { + font-size: 14px; + color: var(--text-light); + margin: 0; +} + +@media (max-width: 768px) { + .service-header { + flex-direction: column; + text-align: center; + } + + .insurance-services { + grid-template-columns: 1fr; + } +} + diff --git a/src/pages/Services.tsx b/src/pages/Services.tsx new file mode 100644 index 0000000..5183e5d --- /dev/null +++ b/src/pages/Services.tsx @@ -0,0 +1,535 @@ +import './Services.css' + +function Services() { + return ( +
+
+
+

我们的服务

+

为申办者提供全面的风险管理解决方案

+
+
+ +
+
+

日常支持服务

+ +
+
+
📋
+

知情同意书(ICF)修改支持

+
+
+

服务内容

+

+ 在临床试验过程中,当您需要对知情同意书进行修改时,我们提供专业的保险条款审查和修改建议服务: +

+
    +
  • ✓ 保险条款审查:审查ICF中与保险相关的条款表述,与保险保障要求差异
  • +
  • ✓ 修改建议:从GCP及诉讼实务角度提供专业的修改建议,减少ICF内容与法规、保单条款差异,避免在可能的赔偿诉讼中因ICF签署不当而处于不利地位
  • +
+

服务流程

+
    +
  • 1. 提交ICF修改需求:您向我们提交需要修改的ICF版本及修改原因
  • +
  • 2. 专业审查:我们的业务支持团队对ICF进行专业审查
  • +
  • 3. 提供修改建议:在3-5个工作日内提供详细的修改建议和说明
  • +
  • 4. 沟通确认:与您沟通确认修改方案,确保满足您的需求
  • +
  • 5. 最终确认:确认修改后的ICF符合保险保障要求
  • +
+

注意事项

+

+ ICF修改可能影响保险保障范围,如您计划修改与受试者赔偿、补偿相关内容,建议在修改前提前与我们沟通。 + 重大修改(如涉及保障范围、责任免除等)需要及时通知保险公司。 +

+
+
+ +
+
+
🚀
+

项目启动会支持

+
+
+

服务内容

+

+ 如您需要,我们可以在您的项目启动会上提供保险理赔流程培训(一般为5-10分钟培训), + 帮助项目团队了解保险保障和理赔流程: +

+
    +
  • ✓ 保险保障介绍:向项目团队介绍保单保障范围、责任免除等重要内容
  • +
  • ✓ 理赔流程讲解:详细讲解理赔申请流程、所需资料、处理时效等
  • +
  • ✓ 常见问题解答:解答项目团队关于保险和理赔的常见问题
  • +
  • ✓ 联系方式提供:提供紧急联系方式和报案渠道
  • +
+

服务形式

+
    +
  • ✓ 远程培训:默认情况下,考虑到效率与成本,我们首选远程/线上方式进行培训
  • +
  • ✓ 培训材料:提供培训PPT、服务手册等材料供项目团队参考
  • +
+

预约方式

+

+ 建议在项目启动会前至少1-2周与我们联系,以便安排培训。我们可根据您的项目启动会时间灵活安排培训, + 并根据您的项目特点定制培训内容。 +

+
+
+ +
+
+
👥
+

CRC培训保险理赔流程

+
+
+

培训目标

+

+ 针对临床研究协调员(CRC)进行专业的保险理赔流程培训,确保CRC能够: +

+
    +
  • ✓ 了解保险保障范围和理赔条件
  • +
  • ✓ 掌握理赔申请流程和所需资料
  • +
  • ✓ 能够协助受试者和研究者完成理赔申请
  • +
  • ✓ 在发生保险责任事件时及时、准确地进行报案
  • +
+

培训内容

+

理赔申请流程:

+
    +
  • • 报案时机:什么情况下需要报案,何时报案
  • +
  • • 报案方式:通过什么方式报案,联系谁
  • +
  • • 报案信息准备:需要准备哪些基本信息
  • +
  • • 案件跟踪:如何跟踪案件处理进展
  • +
+

资料收集与整理:

+
    +
  • • 必需资料清单:了解理赔需要哪些资料
  • +
  • • 资料收集方法:如何从医院、申办者处收集资料
  • +
  • • 资料整理规范:如何整理和提交资料
  • +
  • • 资料保密要求:资料收集和传递中的保密要求
  • +
+

培训形式

+
    +
  • ✓ 集中培训:组织CRC集中培训,统一讲解和答疑
  • +
  • ✓ 分中心培训:根据项目需要,到各研究中心进行培训
  • +
  • ✓ 线上培训:提供线上培训课程,方便CRC随时学习
  • +
  • ✓ 培训材料:提供培训手册、操作指南等材料
  • +
+
+
+ +
+
+
📊
+

临床试验项目进度跟踪

+
+
+

服务内容

+
    +
  • ✓ 试验中心变更:出具保险凭证
  • +
  • ✓ 试验方案和知情同意书修订:保险合同要素和条件变更
  • +
  • ✓ 试验进度延迟、暂停或终止:保险合同延期、续保或解除
  • +
+

服务流程

+

+ 试验进度调整、试验方案等涉及试验项目内容变更的,请及时通知我们。 + 提供变更资料,我们会评估后确定是否需要变更保险合同,可能包括保险合同要素、条件的变更以及加收保险费等情形。 + 我们会及时申请保险合同变更、延期、续保等。 +

+

注意事项

+

+ 试验方案等涉及试验相关风险变化,可能会影响到保险合同利益和权力义务(危险程度增加的通知义务)。 + 谨慎起见,请务必及时将此变化通知我们。请与我们保持试验进度的动态跟踪和沟通, + 以免保险合同脱保的风险而影响保险保障。 +

+
+
+ +
+
+
💰
+

自保(专项风险管理基金)

+
+
+

基金设立方式

+

+ 申办者根据项目大小、历史或行业经验,向华泰经纪支付风险管理费采购健康医疗服务 + (例如一个项目3-5万),双方约定健康医疗服务费的使用范围、对象、执行和审批流程、额度等要素。 +

+

管理特点

+
    +
  • ✓ 多退少补原则,灵活管理
  • +
  • ✓ 以医院出具的医疗费用清单为结算凭证
  • +
  • ✓ 支持比例费用和案件费用两种模式
  • +
  • ✓ 比例费用:整体费用的12-15%
  • +
  • ✓ 案件费用:针对大额、疑难案件收取服务费用
  • +
+
+
+ +
+
+
📉
+

风险减量服务

+
+
+

服务内容

+
    +
  • ✓ 针对临床试验链条的各个环节进行风险点检查
  • +
  • ✓ 人员培训,提升风险管理能力
  • +
  • ✓ 方案完善建议,优化试验设计
  • +
  • ✓ 主动识别和预防潜在风险
  • +
+

服务价值

+

+ 通过系统化的风险减量服务,帮助申办者主动识别和降低风险, + 减少不良事件的发生,保障试验顺利进行。 +

+
+
+ +
+
+
🔄
+

外溢风险管理服务

+
+
+

全流程管理

+

针对受试者出险后的全流程管理,包括:

+
    +
  • ✓ 及时联系受试者和医院
  • +
  • ✓ 安抚受试者情绪
  • +
  • ✓ 安排就医和医疗救治
  • +
  • ✓ 沟通诉求和合理预期
  • +
  • ✓ 协调各方关系
  • +
+

响应时效

+

+ 正常情况下24小时内与医院、受试者沟通,并通知申办者。 + 如遇特殊情况(受试者非集中、境外、无法联系、不可抗力等),采用应急处理模式。 +

+
+
+ +
+
+
🛡️
+

保险服务

+
+
+

服务内容

+
+
+

保险合同审查

+

专业审查保险合同条款,确保保障范围合理

+
+
+

理赔审查

+

协助理赔流程,确保快速高效

+
+
+

保险条款修订

+

根据实际需求修订保险条款

+
+
+

理赔规则制定

+

制定合理的理赔规则和标准

+
+
+

团体标准

+

参与制定行业团体标准

+
+
+

医疗直付服务

+

+ 华泰力争逐步做到与医院实现直付,华泰长期为日本多家保险公司提供此类服务, + 日本人在中国的转诊、医疗推荐、费用结算都是华泰完成,此业务已开展近30年, + 且具有外币结算资质。 +

+
+
+
+
+ +
+
+

理赔服务流程

+

我们的理赔服务遵循标准化、专业化、透明化的处理流程,确保每个案件都得到及时、高效的处理

+ +
+
+
📞
+

第一阶段:报案(步骤C)

+
+
+

C.1 案件接收与初步沟通

+
    +
  • ✓ 接到理赔案件当日完成案件登记
  • +
  • ✓ 记录保单与理赔申请人基本信息
  • +
  • ✓ 收集保单信息(保单号、保险期限、承保人)
  • +
  • ✓ 收集报案信息(报案来源、报案人姓名及联系方式)
  • +
  • ✓ 收集信息提供人联系方式(医院端、申办者端)
  • +
+ +

C.2 医院端信息收集

+

与指定的医院端信息提供人(通常为CRC)联系,收集以下信息:

+

项目信息:

+
    +
  • • 临床试验方案全称、方案编号/版本号
  • +
  • • 产品名称、产品类型、研发阶段
  • +
  • • 研究中心名称、中心编号
  • +
+

受试者信息:

+
    +
  • • 受试者编号、姓名缩写、年龄/性别
  • +
  • • 入组日期、退出日期、当前状态
  • +
  • • 曾签署几次ICF
  • +
+

事件信息:

+
    +
  • • 研究者获知事件时间、上报申办者时间
  • +
  • • 是否报告伦理、报告伦理时间
  • +
  • • 事件名称、事件结局
  • +
  • • 是否方案偏离/违背
  • +
  • • 研究者对相关性评价
  • +
+ +

C.3 申办者端信息收集

+

与指定的企业端信息提供人(通常为PV团队)联系,收集以下信息:

+

事件信息与申办方判断:

+
    +
  • • 事件名称、申办者相关性评价
  • +
  • • 预期性、严重性
  • +
  • • 是否报告监管、报告监管时间
  • +
  • • 是否揭盲、揭盲结果
  • +
+
+
+ +
+
+
🔍
+

第二阶段:案件评估(步骤A)

+
+
+

+ 所有理赔申请在获得基本信息后,均需进行专业评估。对于SUSAR案件,将完成表格中的每一项评估; + 对于其他理赔申请,也将参考该评估体系,确保获得足够的判断依据。 +

+

A.1 材料完整性评估

+

评估所需材料是否完整,包括:

+
    +
  • • 投保时试验方案 vs 最新版本试验方案
  • +
  • • 投保时研究者手册 vs 最新版研究者手册
  • +
  • • 已签署的ICF
  • +
  • • 被保险人与试验机构协议
  • +
  • • PI签署过的SAE表
  • +
  • • 申办者评估后SUSAR
  • +
  • • 递交监管的XML、ACK文件
  • +
  • • AE/SAE治疗过程及费用
  • +
+ +

A.2-A.6 专业评估

+
    +
  • 方案与IB变更情况评估:对比投保时的方案、IB与受试者入组时的方案、IB,关注是否存在重大变更
  • +
  • 受试者入组情况评估:评估受试者编号、入组情况与保单覆盖的受试者数量是否匹配
  • +
  • 方案违背情况评估:判断是否存在严重的方案违背
  • +
  • SUSAR判断:检查并确认是否为SUSAR报告
  • +
  • 事件原因研究:对相关性进行进一步研究,判断相关程度
  • +
+ +

A.7 综合结论

+

+ 对上述评估进行总结,做出是否按SUSAR理赔的建议。无论SUSAR或非SUSAR, + 决定需进行赔偿时,则继续进入赔偿方案设计阶段。 +

+
+
+ +
+
+
💼
+

第三阶段:赔偿方案设计(步骤L)

+
+
+

L.1 受试者赔偿期望收集

+

了解受试者希望的赔偿金额以及赔偿理由,包括:

+
    +
  • • 长期医疗费用
  • +
  • • 短期医疗费用
  • +
  • • 残疾赔偿
  • +
  • • 其他诉求
  • +
+ +

L.2 医疗费用理算

+

基于医疗记录与医疗费用,对实际花费的医疗费用进行梳理、计算:

+
    +
  • • 详细计算每一发票、收费单
  • +
  • • 预估未来大概率发生的费用
  • +
  • • 计算费用总额
  • +
  • • 确定应扣除金额
  • +
+ +

L.3 责任比例调节

+

根据以下情况,与受试者沟通,确定合理的责任比例:

+
    +
  • • ICF告知清晰度
  • +
  • • 是否存在方案违背情况
  • +
  • • 与试验药物或临床试验的相关性
  • +
  • • 是否存在其他可证实的致损原因
  • +
  • • 是否存在超出保障范围的情形
  • +
+ +

L.4 申办者意见确认

+

与申办者沟通确认期望的解决方案和谈判策略:

+
    +
  • • 对赔偿部分的意见
  • +
  • • 是否同意动用补偿额度(附加险)
  • +
  • • 愿意动用的补偿额度
  • +
  • • 赔偿后补偿额度剩余情况
  • +
+ +

L.5 和解协议拟定

+

基于前述评估,拟定和解协议:

+
    +
  • • 确定协议方
  • +
  • • 确定协议金额
  • +
  • • 明确关键条款
  • +
  • • 收集支付合法性的支持文件(如非直接支付给受试者)
  • +
+

协议签署后,进行赔偿支付,案件处理完成。

+
+
+
+
+ +
+
+

报案申请指南

+ +
+
+
📝
+

报案方式

+
+
+

您可以通过以下方式向我们报案:

+
    +
  • 医院端报案:由研究中心(医院)的CRC或PI直接报案
  • +
  • 申办者端报案:由申办者的药物警戒(PV)团队报案
  • +
  • CRO报案:由委托的CRO机构代为报案
  • +
+
+
+ +
+
+
📋
+

报案所需信息

+
+
+

请准备以下信息以便快速完成报案:

+ +

保单信息

+
    +
  • • 承保保险公司名称
  • +
  • • 保单号
  • +
  • • 保险期限(格式:YYYY-MM-DD 至 YYYY-MM-DD)
  • +
+ +

报案信息

+
    +
  • • 报案来源(医院 / 申办者 / CRO / 其他)
  • +
  • • 报案人姓名、岗位、联系电话、邮箱
  • +
+ +

试验项目信息

+
    +
  • • 临床试验方案全称
  • +
  • • 产品名称(试验药物/器械名称)
  • +
  • • 研究中心名称
  • +
  • • 中心编号
  • +
+ +

受试者信息

+
    +
  • • 受试者编号
  • +
  • • 姓名缩写(拼音首字母,如:ZHANG S)
  • +
  • • 入组日期(格式:YYYY-MM-DD)
  • +
  • • 退出日期(如适用)
  • +
+ +

事件信息

+
    +
  • • 事件名称(如:严重肝损伤)
  • +
  • • 事件发生时间(格式:YYYY-MM-DD)
  • +
  • • 事件结局(康复/后遗症/死亡/失访/其他)
  • +
  • • 是否方案偏离/违背
  • +
+ +

资料提供方式

+
    +
  • • 优先方式:通过邮件发送电子版文件
  • +
  • • 分次提供:如无法一次性提供,可分多次发送
  • +
  • • 原件要求:部分文件可能需要提供原件或加盖公章的复印件
  • +
+
+
+
+
+ +
+
+

服务时效承诺

+ +
+
+
⏱️
+

响应时效承诺

+
+
+
    +
  • 案件接收:接到报案后,当日完成案件登记并开始处理
  • +
  • 信息收集:在获得准许联系后,立即启动信息收集流程
  • +
  • 初步评估:在收集到基本信息后,3个工作日内完成初步评估
  • +
  • 详细评估:在收集到完整资料后,7-10个工作日内完成详细评估
  • +
  • 方案设计:在完成评估后,5个工作日内提出赔偿方案建议
  • +
  • 沟通反馈:所有咨询和问题,24小时内给予回复
  • +
  • 紧急案件:接到报案后立即响应
  • +
+
+
+ +
+
+
+

专业服务承诺

+
+
+
    +
  • 专业团队:由具备临床试验、医学、法律背景的专业人员提供服务
  • +
  • 标准化流程:遵循标准化的处理流程,确保每个案件都得到规范处理
  • +
  • 透明沟通:及时向您通报案件处理进展,重要决策前充分沟通
  • +
  • 质量保证:建立质量检查机制,确保评估结果的准确性和公正性
  • +
+
+
+ +
+
+
🔒
+

保密承诺

+
+
+
    +
  • 信息保密:严格保护受试者个人信息,不向无关方泄露
  • +
  • 商业保密:保护申办者的商业信息和试验数据
  • +
  • 合规处理:所有信息处理均符合相关法律法规要求
  • +
+
+
+
+
+
+ ) +} + +export default Services + diff --git a/src/pages/SmartAcquisition.tsx b/src/pages/SmartAcquisition.tsx new file mode 100644 index 0000000..ac51fe3 --- /dev/null +++ b/src/pages/SmartAcquisition.tsx @@ -0,0 +1,28 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './RiskData.css' + +function SmartAcquisition() { + return ( + +
+ +
+
+
+
+

建设中

+
+
+
+
+
+
+ ) +} + +export default SmartAcquisition diff --git a/src/pages/Sponsor.css b/src/pages/Sponsor.css new file mode 100644 index 0000000..7b8f55c --- /dev/null +++ b/src/pages/Sponsor.css @@ -0,0 +1,262 @@ +.hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--white); + padding: 100px 0 60px; + text-align: center; +} + +.hero-section .section-title { + color: var(--white); +} + +.hero-section .section-subtitle { + color: rgba(255, 255, 255, 0.9); +} + +.responsibility-overview { + list-style: none; + padding-left: 0; + margin: 15px 0 0; +} + +.responsibility-overview li { + padding: 8px 0; + padding-left: 1.5em; + position: relative; + line-height: 1.6; +} + +.responsibility-overview li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--primary-color); +} + +.risk-section { + margin: 30px 0; + padding: 20px 0; + border-bottom: 1px solid var(--border-color); +} + +.risk-section:last-child { + border-bottom: none; +} + +.risk-section h3 { + font-size: 24px; + margin-bottom: 20px; + color: var(--primary-color); +} + +.risk-categories { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; + margin-top: 20px; +} + +.risk-category { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; +} + +.risk-category h4 { + font-size: 18px; + margin-bottom: 15px; + color: var(--text-color); +} + +.risk-category ul { + list-style: none; + padding-left: 20px; +} + +.risk-category li { + padding: 8px 0; + font-size: 15px; + color: var(--text-color); + line-height: 1.6; +} + +.risk-category ul ul { + margin-top: 5px; + padding-left: 20px; +} + +.strategy-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.strategy-card { + background: var(--bg-color); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.strategy-card h4 { + font-size: 20px; + margin-bottom: 10px; + color: var(--primary-color); +} + +.strategy-card p { + font-size: 15px; + color: var(--text-color); + margin: 0; +} + +.solution-section { + background: var(--bg-color); +} + +.solution-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 30px; +} + +.solution-card h3 { + font-size: 24px; + margin-bottom: 20px; + color: var(--primary-color); +} + +.solution-card ul { + list-style: none; + padding-left: 20px; + margin: 15px 0; +} + +.solution-card li { + padding: 8px 0; + font-size: 15px; + color: var(--text-color); +} + +.process-section { + background: var(--white); +} + +.process-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin-top: 40px; +} + +.process-step { + text-align: center; + position: relative; +} + +.step-number { + width: 60px; + height: 60px; + background: var(--primary-color); + color: var(--white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + font-weight: bold; + margin: 0 auto 20px; +} + +.process-step h3 { + font-size: 20px; + margin-bottom: 15px; + color: var(--text-color); +} + +.process-step p { + font-size: 15px; + color: var(--text-light); + line-height: 1.6; +} + +.team-section { + background: var(--bg-color); +} + +.team-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.team-card { + background: var(--white); + padding: 25px; + border-radius: 8px; + border-left: 4px solid var(--primary-color); +} + +.team-card h4 { + font-size: 20px; + margin-bottom: 15px; + color: var(--primary-color); +} + +.team-card p { + font-size: 15px; + color: var(--text-color); + line-height: 1.6; + margin: 0; +} + +.team-section .card h3 { + font-size: 24px; + margin-bottom: 20px; + color: var(--primary-color); +} + +.team-section .card h4 { + font-size: 20px; + margin: 25px 0 15px; + color: var(--text-color); +} + +.team-section .service-list { + list-style: none; + padding: 0; + margin: 15px 0; +} + +.team-section .service-list li { + padding: 10px 0; + font-size: 16px; + color: var(--text-color); +} + +.team-section .service-list a { + color: var(--primary-color); + text-decoration: none; +} + +.team-section .service-list a:hover { + text-decoration: underline; +} + +@media (max-width: 768px) { + .risk-categories, + .solution-grid { + grid-template-columns: 1fr; + } + + .process-steps { + grid-template-columns: 1fr; + } + + .team-grid { + grid-template-columns: 1fr; + } +} + diff --git a/src/pages/Sponsor.tsx b/src/pages/Sponsor.tsx new file mode 100644 index 0000000..de99582 --- /dev/null +++ b/src/pages/Sponsor.tsx @@ -0,0 +1,137 @@ +import PageContainer from '../components/PageContainer' +import PageHeader from '../components/PageHeader' +import './Sponsor.css' + +function Sponsor() { + return ( + +
+ +
+
+
+
+

申办者职责概述

+
    +
  • 承担受试者保护的主要责任
  • +
  • 提供法律上、经济上的保险或保证
  • +
  • 建立完善的风险管理体系
  • +
  • 确保临床试验的质量和安全
  • +
+
+
+
+ +
+
+
+

风险管理体系

+ +
+

风险识别

+
+
+

从临床试验药物角度

+
    +
  • AE(不良事件):为证明与药物相关
  • +
  • ADR(药物不良反应):已证明与药物相关 +
      +
    • SADR(严重药物不良反应):严重的ADR
    • +
    • ADR写入IB的RSI(参考安全信息)
    • +
    +
  • +
  • SUSAR(可疑且非预期的严重不良反应): +
      +
    • 新发现的严重不良反应,需及时报告
    • +
    • 未写入RSI的严重不良反应属于SUSAR
    • +
    +
  • +
+
+
+

从临床试验操作角度

+
    +
  • 临床试验方案合理性
  • +
  • 临床试验方案操作是否符合规定
  • +
  • 医疗行为是否合理
  • +
  • 临床试验组织管理是否合理
  • +
+
+
+

其他相关风险

+
    +
  • 行为原则
  • +
  • 心理原因
  • +
  • 其他
  • +
+
+
+
+ +
+

风险评估

+

+ 华泰经纪协助申办者基于项目复杂度和风险度厘清首要风险与次要风险, + 通过专业的风险评估工具和方法,为项目制定合适的风险管理策略。 +

+
+ +
+

风险管理策略

+
+
+

自留策略

+

对于可接受的风险,采用自留方式管理

+
+
+

保险策略

+

对于首要风险,通过保险进行风险转移

+
+
+

合同策略

+

通过合同约定,明确各方责任和风险分担

+
+
+
+
+
+
+ +
+
+

操作流程

+
+
+
1
+

项目风险评估

+

华泰经纪协助申办者基于项目复杂度和风险度厘清首要风险与次要风险

+
+
+
2
+

投保流程

+

通过华泰保险经纪完成首要风险投保

+
+
+
3
+

专项风险管理基金设立

+

申办者支付风险管理费,设立专项风险管理基金(3-5万/项目)

+
+
+
4
+

理赔/补偿流程

+

受试者出险后,24小时内响应,快速处理理赔和补偿

+
+
+
+
+
+
+
+ ) +} + +export default Sponsor + diff --git a/src/pages/dashboard/ClaimDetail.css b/src/pages/dashboard/ClaimDetail.css new file mode 100644 index 0000000..b246687 --- /dev/null +++ b/src/pages/dashboard/ClaimDetail.css @@ -0,0 +1,157 @@ +.claim-detail-page { + min-height: 100%; +} + +.detail-section { + margin-bottom: var(--padding-xl, 24px); +} + +.section-title { + font-size: 20px; + font-weight: 600; + margin-bottom: var(--padding-md, 16px); + color: var(--text-color, #333); +} + +.info-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--padding-lg, 20px); +} + +.info-item { + display: flex; + flex-direction: column; + gap: var(--padding-xs, 8px); +} + +.info-item.full-width { + grid-column: 1 / -1; +} + +.info-item label { + font-weight: 600; + color: var(--text-light, #666); + font-size: 14px; +} + +.info-item span { + color: var(--text-color, #333); + font-size: 16px; +} + +.info-item .amount { + font-size: 20px; + font-weight: 600; + color: var(--brand-primary, #0ea5e9); +} + +.status-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.status-pending { + background: #e6f0ff; + color: #0ea5e9; +} + +.documents-list { + display: flex; + flex-direction: column; + gap: var(--padding-md, 16px); +} + +.document-item { + display: flex; + align-items: center; + gap: var(--padding-md, 16px); + padding: var(--padding-md, 16px); + border: 1px solid var(--border-color, #e8e8e8); + border-radius: 8px; + transition: all 0.2s; +} + +.document-item:hover { + background: rgba(14, 165, 233, 0.02); + border-color: var(--brand-primary, #0ea5e9); +} + +.document-icon { + font-size: 32px; +} + +.document-info { + flex: 1; +} + +.document-name { + font-weight: 500; + color: var(--text-color, #333); + margin-bottom: 4px; +} + +.document-type { + font-size: 12px; + color: var(--text-light, #666); +} + +.document-actions { + display: flex; + gap: var(--padding-xs, 8px); +} + +.btn-link { + background: transparent; + color: var(--brand-primary, #0ea5e9); + border: none; + cursor: pointer; + text-decoration: underline; + padding: 0; +} + +.btn-link:hover { + color: var(--brand-primary-dark, #0284c7); +} + +.action-bar { + display: flex; + justify-content: flex-end; + gap: var(--padding-md, 16px); + margin-top: var(--padding-xl, 24px); + padding-top: var(--padding-xl, 24px); + border-top: 1px solid var(--border-color, #e8e8e8); +} + +.btn { + padding: var(--padding-md, 16px) var(--padding-lg, 20px); + border-radius: 4px; + font-size: 16px; + cursor: pointer; + transition: all 0.2s; + text-decoration: none; + display: inline-block; +} + +.btn-primary { + background: var(--brand-primary, #0ea5e9); + color: #fff; + border: none; +} + +.btn-primary:hover { + background: var(--brand-primary-dark, #0284c7); +} + +.btn-secondary { + background: #fff; + color: var(--brand-primary, #0ea5e9); + border: 1px solid var(--brand-primary, #0ea5e9); +} + +.btn-secondary:hover { + background: rgba(14, 165, 233, 0.05); +} diff --git a/src/pages/dashboard/ClaimDetail.tsx b/src/pages/dashboard/ClaimDetail.tsx new file mode 100644 index 0000000..d2e1b4c --- /dev/null +++ b/src/pages/dashboard/ClaimDetail.tsx @@ -0,0 +1,142 @@ +import { useParams, Link } from 'react-router-dom' +import { useAuth } from '../../contexts/AuthContext' +import PageContainer from '../../components/PageContainer' +import PageHeader from '../../components/PageHeader' +import './ClaimDetail.css' + +function ClaimDetail() { + const { id } = useParams() + const { user } = useAuth() + const isPolicyholder = user?.role === '投保人' + const _isInsurer = user?.role === '保险人' + void _isInsurer // 预留:保险人视图 + + // 模拟数据(后续替换为 API 调用) + const mockClaim = { + id: id || '1', + projectNumber: 'CT-2025-001', + projectTitle: 'XX药物临床试验项目', + coverage: '全面保障', + insurer: '太平洋保险', + status: '待处理', + details: { + claimNumber: 'CL-2025-001', + claimDate: '2025-02-01', + claimAmount: '50000', + claimReason: '受试者发生不良事件,需要医疗费用报销', + description: '理赔详细描述信息...' + }, + documents: [ + { name: '理赔申请书', type: 'pdf', url: '#' }, + { name: '医疗费用清单', type: 'xlsx', url: '#' }, + { name: '医院诊断证明', type: 'pdf', url: '#' } + ] + } + + return ( + +
+ +
+ {/* 基本信息 */} +
+

基本信息

+
+
+
+ + {mockClaim.projectNumber} +
+
+ + {mockClaim.projectTitle} +
+
+ + {mockClaim.coverage} +
+
+ + {mockClaim.insurer} +
+
+ + {mockClaim.details.claimNumber} +
+
+ + {mockClaim.details.claimDate} +
+
+ + ¥{mockClaim.details.claimAmount} +
+
+ + {mockClaim.status} +
+
+ + {mockClaim.details.claimReason} +
+
+ + {mockClaim.details.description} +
+
+
+
+ + {/* 相关文档 */} +
+

相关文档

+
+
+ {mockClaim.documents.map((doc, index) => ( +
+
+ {doc.type === 'pdf' ? '📄' : '📊'} +
+
+
{doc.name}
+
{doc.type.toUpperCase()}
+
+
+ + 下载 + + +
+
+ ))} +
+
+
+ + {/* 操作按钮 */} +
+ + 返回列表 + + {isPolicyholder ? ( + + ) : ( + + )} +
+
+
+
+ ) +} + +export default ClaimDetail diff --git a/src/pages/dashboard/ClaimProgress.css b/src/pages/dashboard/ClaimProgress.css new file mode 100644 index 0000000..8fca8f0 --- /dev/null +++ b/src/pages/dashboard/ClaimProgress.css @@ -0,0 +1,101 @@ +.claim-progress-page { + min-height: 100%; +} + +.table-container { + overflow-x: auto; +} + +.data-table { + width: 100%; + border-collapse: collapse; +} + +.data-table thead { + background: #f5f5f5; +} + +.data-table th { + padding: var(--padding-md, 16px); + text-align: left; + font-weight: 600; + color: var(--text-color, #333); + border-bottom: 2px solid var(--border-color, #e8e8e8); +} + +.data-table td { + padding: var(--padding-md, 16px); + border-bottom: 1px solid var(--border-color, #e8e8e8); + color: var(--text-color, #333); +} + +.data-table tbody tr:hover { + background: rgba(14, 165, 233, 0.02); +} + +.status-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.status-success { + background: #e6f7e6; + color: #3c3; +} + +.status-badge.status-warning { + background: #fff7e6; + color: #c93; +} + +.status-badge.status-pending { + background: #e6f0ff; + color: #0ea5e9; +} + +.action-buttons { + display: flex; + gap: var(--padding-xs, 8px); +} + +.btn-sm { + padding: var(--padding-xs, 8px) var(--padding-md, 16px); + font-size: 14px; +} + +.btn-primary { + background: var(--brand-primary, #0ea5e9); + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background 0.2s; + text-decoration: none; + display: inline-block; +} + +.btn-primary:hover { + background: var(--brand-primary-dark, #0284c7); +} + +.btn-secondary { + background: #fff; + color: var(--brand-primary, #0ea5e9); + border: 1px solid var(--brand-primary, #0ea5e9); + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +.btn-secondary:hover { + background: rgba(14, 165, 233, 0.05); +} + +.empty-state { + text-align: center; + padding: var(--padding-xl, 24px); + color: var(--text-light, #666); +} diff --git a/src/pages/dashboard/ClaimProgress.tsx b/src/pages/dashboard/ClaimProgress.tsx new file mode 100644 index 0000000..ef66372 --- /dev/null +++ b/src/pages/dashboard/ClaimProgress.tsx @@ -0,0 +1,126 @@ +import { Link } from 'react-router-dom' +import { useAuth } from '../../contexts/AuthContext' +import PageContainer from '../../components/PageContainer' +import PageHeader from '../../components/PageHeader' +import './ClaimProgress.css' + +function ClaimProgress() { + const { user } = useAuth() + const isPolicyholder = user?.role === '投保人' + const _isInsurer = user?.role === '保险人' + void _isInsurer // 预留:保险人视图 + + // 模拟数据(后续替换为 API 调用) + const mockClaims = [ + { + id: '1', + projectNumber: 'CT-2025-001', + projectTitle: 'XX药物临床试验项目', + coverage: '全面保障', + insurer: '太平洋保险', + status: '待处理' + }, + { + id: '2', + projectNumber: 'CT-2025-002', + projectTitle: 'YY药物临床试验项目', + coverage: '基础保障', + insurer: '中国大地保险', + status: '处理中' + }, + { + id: '3', + projectNumber: 'CT-2025-003', + projectTitle: 'ZZ药物临床试验项目', + coverage: '全面保障', + insurer: '华泰财产', + status: '已完成' + } + ] + + const getStatusClass = (status: string) => { + switch (status) { + case '已完成': + return 'status-success' + case '处理中': + return 'status-warning' + case '待处理': + return 'status-pending' + default: + return '' + } + } + + return ( + +
+ +
+
+
+ + + + + + + + + + + + + {mockClaims.length > 0 ? ( + mockClaims.map(claim => ( + + + + + + + + + )) + ) : ( + + + + )} + +
项目编号试验题目保障范围承保公司承保状态操作
{claim.projectNumber}{claim.projectTitle}{claim.coverage}{claim.insurer} + + {claim.status} + + +
+ + 查看明细 + + {isPolicyholder ? ( + + ) : ( + + )} +
+
+ 暂无理赔记录 +
+
+
+
+
+
+ ) +} + +export default ClaimProgress diff --git a/src/pages/dashboard/CoverageAssessment.css b/src/pages/dashboard/CoverageAssessment.css new file mode 100644 index 0000000..89702bd --- /dev/null +++ b/src/pages/dashboard/CoverageAssessment.css @@ -0,0 +1,107 @@ +.coverage-assessment-page { + min-height: 100%; +} + +.assessment-list { + margin-top: var(--padding-md, 16px); +} + +.table-header { + background: rgba(14, 165, 233, 0.05); + border-bottom: 2px solid var(--border-color, #e8e8e8); +} + +.table-row { + display: grid; + grid-template-columns: 150px 150px 1fr 200px 200px 120px 200px; + gap: var(--padding-md, 16px); + padding: var(--padding-md, 16px); + align-items: center; + border-bottom: 1px solid var(--border-color, #e8e8e8); +} + +.table-row:last-child { + border-bottom: none; +} + +.table-row:hover { + background: rgba(14, 165, 233, 0.02); +} + +.header-row { + font-weight: 500; + color: var(--text-color, #333); +} + +.col { + font-size: 14px; +} + +.col-title { + font-weight: 500; + color: var(--text-color, #333); +} + +.status-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.status-completed { + background: #efe; + color: #3c3; +} + +.status-badge.status-pending { + background: #ffe; + color: #c93; +} + +.status-badge.status-rejected { + background: #fee; + color: #c33; +} + +.col-actions { + display: flex; + gap: var(--padding-xs, 8px); +} + +.btn-danger { + background: #f33; + color: #fff; + border: none; + padding: var(--padding-xs, 8px) var(--padding-md, 16px); + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; +} + +.btn-danger:hover { + background: #c33; +} + +.empty-state { + padding: var(--padding-xl, 24px); + text-align: center; + color: var(--text-light, #666); +} + +/* 响应式设计 */ +@media (max-width: 1400px) { + .table-row { + grid-template-columns: 120px 120px 1fr 150px 150px 100px 180px; + font-size: 13px; + } +} + +@media (max-width: 768px) { + .table-row { + grid-template-columns: 1fr; + gap: var(--padding-xs, 8px); + } +} diff --git a/src/pages/dashboard/CoverageAssessment.tsx b/src/pages/dashboard/CoverageAssessment.tsx new file mode 100644 index 0000000..5c45ec4 --- /dev/null +++ b/src/pages/dashboard/CoverageAssessment.tsx @@ -0,0 +1,115 @@ +import PageContainer from '../../components/PageContainer' +import PageHeader from '../../components/PageHeader' +import './CoverageAssessment.css' + +function CoverageAssessment() { + // 模拟数据(后续替换为 API 调用) + const mockAssessments = [ + { + id: '1', + sponsor: 'XX制药有限公司', + projectNumber: 'CT-2025-003', + title: '新药III期临床试验', + coverage: 'SUSAR、SAE、医疗费用', + specialTerms: '包含境外受试者保障', + status: '已成交', + statusType: 'completed' + }, + { + id: '2', + sponsor: 'YY生物技术公司', + projectNumber: 'CT-2025-004', + title: '生物制品II期临床试验', + coverage: 'SUSAR、医疗费用', + specialTerms: '无', + status: '待确认', + statusType: 'pending' + }, + { + id: '3', + sponsor: 'ZZ创新药企', + projectNumber: 'CT-2025-005', + title: '创新药I期临床试验', + coverage: 'SUSAR、SAE', + specialTerms: '高风险项目,需额外评估', + status: '已拒绝', + statusType: 'rejected' + } + ] + + const getStatusClass = (statusType: string) => { + switch (statusType) { + case 'completed': + return 'status-completed' + case 'pending': + return 'status-pending' + case 'rejected': + return 'status-rejected' + default: + return '' + } + } + + return ( + +
+ +
+
+
+
+
+
+
申办者名称
+
项目编号
+
试验题目
+
保障条款
+
特别约定
+
成交情况
+
操作
+
+
+
+ {mockAssessments.length > 0 ? ( + mockAssessments.map(assessment => ( +
+
{assessment.sponsor}
+
{assessment.projectNumber}
+
{assessment.title}
+
{assessment.coverage}
+
{assessment.specialTerms}
+
+ + {assessment.status} + +
+
+ + {assessment.statusType === 'pending' && ( + <> + + + + )} +
+
+ )) + ) : ( +
+

暂无保障评估数据

+
+ )} +
+
+
+
+
+
+
+ ) +} + +export default CoverageAssessment diff --git a/src/pages/dashboard/Dashboard.css b/src/pages/dashboard/Dashboard.css new file mode 100644 index 0000000..cdd4500 --- /dev/null +++ b/src/pages/dashboard/Dashboard.css @@ -0,0 +1,217 @@ +.dashboard-page { + min-height: 100%; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--padding-lg, 20px); + margin-bottom: var(--padding-xl, 24px); +} + +.stat-card { + background: #fff; + border-radius: 8px; + padding: var(--padding-lg, 20px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid var(--border-color, #e8e8e8); + display: flex; + align-items: flex-start; + gap: var(--padding-md, 16px); + transition: box-shadow 0.2s, filter 0.2s, border-color 0.2s; +} + +.stat-card:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); + filter: brightness(1.05); + border-color: var(--brand-primary, #0ea5e9); +} + +.stat-icon { + font-size: 40px; + line-height: 1; +} + +.stat-content { + flex: 1; +} + +.stat-content h3 { + margin: 0 0 var(--padding-xs, 8px) 0; + font-size: 16px; + color: var(--text-light, #666); + font-weight: 500; +} + +.stat-value { + font-size: 32px; + font-weight: bold; + color: var(--brand-primary, #0ea5e9); + margin-bottom: var(--padding-xs, 8px); +} + +.stat-details { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 14px; + color: var(--text-light, #666); +} + +.stat-link { + color: var(--brand-primary, #0ea5e9); + text-decoration: none; + font-size: 14px; + align-self: flex-end; + transition: color 0.2s; +} + +.stat-link:hover { + color: var(--brand-primary-dark, #0284c7); +} + +.quick-actions { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--padding-lg, 20px); + margin-top: var(--padding-md, 16px); +} + +.quick-action-card, +.quick-action-button { + background: #fff; + border-radius: 8px; + padding: var(--padding-xl, 24px); + text-decoration: none; + color: var(--text-color, #333); + text-align: left; + cursor: pointer; + border: 1px solid var(--border-color, #e5e7eb); + transition: box-shadow 0.2s ease, border-color 0.2s ease, filter 0.2s ease; +} + +.quick-action-button { + font: inherit; + width: 100%; + display: block; +} + +.quick-action-card { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + text-align: center; +} + +.quick-action-card:hover, +.quick-action-button:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); + filter: brightness(1.05); + border-color: var(--brand-primary, #0ea5e9); + color: var(--brand-primary, #0ea5e9); +} + +.action-icon { + font-size: 48px; + margin-bottom: var(--padding-md, 16px); +} + +.quick-action-card h3 { + margin: 0 0 var(--padding-xs, 8px) 0; + font-size: 18px; +} + +.quick-action-card p { + margin: 0; + font-size: 14px; + color: var(--text-light, #666); +} + +.tasks-list { + margin-top: var(--padding-md, 16px); +} + +.task-items { + list-style: none; + padding: 0; + margin: 0; +} + +.task-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--padding-md, 16px); + border-bottom: 1px solid var(--border-color, #e8e8e8); + transition: background 0.2s; +} + +.task-item:last-child { + border-bottom: none; +} + +.task-item:hover { + background: rgba(14, 165, 233, 0.02); +} + +.task-info { + flex: 1; +} + +.task-title { + margin: 0 0 var(--padding-xs, 8px) 0; + font-size: 16px; + font-weight: 500; + color: var(--text-color, #333); +} + +.task-meta { + display: flex; + gap: var(--padding-md, 16px); + font-size: 14px; + color: var(--text-light, #666); +} + +.task-type { + padding: 2px 8px; + background: rgba(14, 165, 233, 0.1); + color: var(--brand-primary, #0ea5e9); + border-radius: 4px; +} + +.task-actions { + display: flex; + align-items: center; + gap: var(--padding-md, 16px); +} + +.task-priority { + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.task-priority.priority-high { + background: #fee; + color: #c33; +} + +.task-priority.priority-medium { + background: #ffe; + color: #c93; +} + +.task-priority.priority-low { + background: #efe; + color: #3c3; +} + +.btn-sm { + padding: var(--padding-xs, 8px) var(--padding-md, 16px); + font-size: 14px; +} + +.empty-state { + padding: var(--padding-xl, 24px); + text-align: center; + color: var(--text-light, #666); +} diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx new file mode 100644 index 0000000..5948796 --- /dev/null +++ b/src/pages/dashboard/Dashboard.tsx @@ -0,0 +1,174 @@ +import { Link } from 'react-router-dom' +import { useAuth } from '../../contexts/AuthContext' +import { useQuoteModal } from '../../contexts/QuoteModalContext' +import PageContainer from '../../components/PageContainer' +import PageHeader from '../../components/PageHeader' +import './Dashboard.css' + +function Dashboard() { + const { user } = useAuth() + const { openQuoteModal } = useQuoteModal() + const isPolicyholder = user?.role === '投保人' + const _isInsurer = user?.role === '保险人' + + // 投保人数据 + const policyholderData = { + inquiryProjects: 5, + activeCoverage: 8, + allProjects: 15, + tasks: [ + { id: '1', title: '待处理的报价项目', type: '报价', time: '2025-02-01 10:30', priority: 'high', link: '/dashboard/project-quotes' }, + { id: '2', title: '待处理的理赔', type: '理赔', time: '2025-02-01 09:15', priority: 'medium', link: '/dashboard/claims' } + ] + } + + // 保险人数据 + const insurerData = { + inquiryProjects: 12, + activeCoverage: 20, + allProjects: 35, + tasks: [ + { id: '1', title: '待处理的报价项目', type: '报价', time: '2025-02-01 10:30', priority: 'high', link: '/dashboard/inquiries' }, + { id: '2', title: '待处理的理赔', type: '理赔', time: '2025-02-01 09:15', priority: 'medium', link: '/dashboard/claims' } + ] + } + + const data = isPolicyholder ? policyholderData : _isInsurer ? insurerData : policyholderData + + return ( + +
+ +
+ {/* 统计卡片 */} +
+
+
💼
+
+

询价项目

+
{data.inquiryProjects}
+
+ 待处理询价项目 +
+
+ {isPolicyholder ? ( + 查看详情 → + ) : ( + 查看详情 → + )} +
+ +
+
🛡️
+
+

生效保障

+
{data.activeCoverage}
+
+ 已生效保障数量 +
+
+ {isPolicyholder ? ( + 查看详情 → + ) : ( + 查看详情 → + )} +
+ +
+
📋
+
+

全部项目

+
{data.allProjects}
+
+ 包括生效中及已失效的项目 +
+
+ {isPolicyholder ? ( + 查看详情 → + ) : ( + 查看详情 → + )} +
+
+ + {/* 投保人快捷方式 */} + {isPolicyholder && ( +
+
+
+

快捷方式

+
+
+ + +
📋
+

报价页面

+

查看全部保司报价与询价记录

+ + +
📄
+

申请理赔

+

跳转到理赔申请页面(需关联项目)

+ + +
📑
+

方案介绍

+

申请设计并介绍方案

+ + +
🎓
+

培训支持

+

申请培训支持

+ +
+
+
+ )} + + {/* 待办任务列表 */} +
+
+

待处理任务

+
+ {data.tasks.length > 0 ? ( +
    + {data.tasks.map(task => ( +
  • +
    +

    {task.title}

    +
    + {task.type} + {task.time} +
    +
    +
    + + {task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低'} + + 处理 +
    +
  • + ))} +
+ ) : ( +
+

暂无待办任务

+
+ )} +
+
+
+
+
+
+ ) +} + +export default Dashboard diff --git a/src/pages/dashboard/DrugSafetyQuery.css b/src/pages/dashboard/DrugSafetyQuery.css new file mode 100644 index 0000000..fd27b40 --- /dev/null +++ b/src/pages/dashboard/DrugSafetyQuery.css @@ -0,0 +1,43 @@ +.drug-safety-query-page { + min-height: 100%; +} + +.drug-safety-intro h3 { + margin-top: 0; + margin-bottom: var(--padding-md, 16px); + color: var(--text-color, #333); + font-size: 18px; +} + +.drug-safety-intro p { + margin-bottom: var(--padding-md, 16px); + line-height: 1.6; + color: var(--text-color, #333); +} + +.drug-safety-intro p:last-child { + margin-bottom: 0; +} + +.placeholder-block { + margin-top: var(--padding-lg, 24px); + padding: var(--padding-xl, 32px); + background: var(--bg-color, #f5f5f5); + border-radius: 8px; + border: 1px dashed var(--border-color, #e8e8e8); + text-align: center; + color: var(--text-light, #666); +} + +.back-link { + margin-top: var(--padding-lg, 24px); +} + +.back-link a { + color: var(--brand-primary, #0ea5e9); + text-decoration: none; +} + +.back-link a:hover { + text-decoration: underline; +} diff --git a/src/pages/dashboard/DrugSafetyQuery.tsx b/src/pages/dashboard/DrugSafetyQuery.tsx new file mode 100644 index 0000000..3418dcb --- /dev/null +++ b/src/pages/dashboard/DrugSafetyQuery.tsx @@ -0,0 +1,39 @@ +import { Link } from 'react-router-dom' +import PageContainer from '../../components/PageContainer' +import PageHeader from '../../components/PageHeader' +import './DrugSafetyQuery.css' + +function DrugSafetyQuery() { + return ( + +
+ +
+
+
+
+

功能说明(占位)

+

+ 药安查将提供药物安全相关数据的一站式查询,包括但不限于:药品不良反应报告、药物警戒公告、 + 说明书安全信息、国内外监管动态等。当前页面为占位,后续将开放检索与导出功能。 +

+
+

查询入口与数据展示区域开发中,敬请期待。

+
+
+ +
+ ← 返回智能工具 +
+
+
+
+
+
+ ) +} + +export default DrugSafetyQuery diff --git a/src/pages/dashboard/ICFEditor.css b/src/pages/dashboard/ICFEditor.css new file mode 100644 index 0000000..7fce0ae --- /dev/null +++ b/src/pages/dashboard/ICFEditor.css @@ -0,0 +1,102 @@ +.icf-editor-page { + min-height: 100%; +} + +.editor-card { + max-width: 1000px; + margin: 0 auto; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--padding-lg, 20px); + padding-bottom: var(--padding-md, 16px); + border-bottom: 1px solid var(--border-color, #e8e8e8); +} + +.editor-header h3 { + margin: 0; + font-size: 18px; + color: var(--text-color, #333); +} + +.editor-content { + display: flex; + flex-direction: column; + gap: var(--padding-lg, 20px); +} + +.editor-section { + display: flex; + flex-direction: column; + gap: var(--padding-xs, 8px); +} + +.editor-section label { + font-weight: 500; + color: var(--text-color, #333); +} + +.editor-textarea { + width: 100%; + padding: var(--padding-md, 16px); + border: 1px solid var(--border-color, #e8e8e8); + border-radius: 4px; + font-size: 14px; + font-family: inherit; + line-height: 1.6; + resize: vertical; + transition: border-color 0.2s; +} + +.editor-textarea:focus { + outline: none; + border-color: var(--brand-primary, #0ea5e9); +} + +.editor-actions { + display: flex; + gap: var(--padding-md, 16px); +} + +.suggestions-section { + padding: var(--padding-lg, 20px); + background: rgba(14, 165, 233, 0.05); + border-radius: 4px; + border: 1px solid rgba(14, 165, 233, 0.2); +} + +.suggestions-section h4 { + margin: 0 0 var(--padding-md, 16px) 0; + font-size: 16px; + color: var(--brand-primary, #0ea5e9); +} + +.suggestions-list { + margin: 0; + padding-left: var(--padding-xl, 24px); + list-style: disc; +} + +.suggestions-list li { + margin-bottom: var(--padding-xs, 8px); + color: var(--text-color, #333); + line-height: 1.6; +} + +.back-link { + margin-top: var(--padding-xl, 24px); + text-align: center; +} + +.back-link a { + color: var(--brand-primary, #0ea5e9); + text-decoration: none; + font-size: 14px; +} + +.back-link a:hover { + text-decoration: underline; +} diff --git a/src/pages/dashboard/ICFEditor.tsx b/src/pages/dashboard/ICFEditor.tsx new file mode 100644 index 0000000..1ca6fc2 --- /dev/null +++ b/src/pages/dashboard/ICFEditor.tsx @@ -0,0 +1,86 @@ +import { useState } from 'react' +import { Link, Navigate } from 'react-router-dom' +import { useAuth } from '../../contexts/AuthContext' +import PageContainer from '../../components/PageContainer' +import PageHeader from '../../components/PageHeader' +import './ICFEditor.css' + +function ICFEditor() { + const { user } = useAuth() + + // 仅投保人可见 + if (user?.role !== '投保人') { + return + } + const [content, setContent] = useState('') + const [suggestions, setSuggestions] = useState([]) + + const handleAnalyze = () => { + // 模拟分析(后续替换为 API 调用) + setSuggestions([ + '建议在第3条中明确说明试验可能的风险', + '第5条关于受试者权益的描述可以更加详细', + '建议补充关于数据保护的说明' + ]) + } + + return ( + +
+ +
+
+
+
+
+

上传ICF文档

+ +
+ +
+
+ +