first commit

This commit is contained in:
william.wan 2026-02-11 14:07:01 +08:00
commit 7d1cc40d95
156 changed files with 22367 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@ -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?

201
README.md Normal file
View File

@ -0,0 +1,201 @@
# RMO一站式临床试验风险管理网站
## 项目简介
RMORisk 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月

BIN
RMO网站需求文档.docx Normal file

Binary file not shown.

1095
RMO网站需求文档.md Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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《E6R3药物临床试验质量管理规范技术指导原则》即ICH GCP自2019年启动修订已于2025年1月定稿为ICH E6R3。为进一步推动我国药物临床试验高质量发展对2020版GCP进行修订。
二、修订的主要考虑
《药物临床试验质量管理规范》以下简称GCP是国家药监局发布的规范性文件指导监管部门、申请人、药物临床试验机构及其他接受委托的机构开展药物临床试验工作是监管执法依据。修订的主要考虑如下
做好与相关法律法规、其他规范性文件以及技术指导原则衔接。一是法律法规层面与《药品管理法》《疫苗管理法》《药品管理法实施条例》《药品注册管理办法》等上位法做好对接。二是规范性文件层面与《药物临床试验机构管理规定》《药物临床试验机构监督检查办法试行》、国家卫生健康委等四部门发布的《涉及人的生命科学和医学研究伦理审查办法》等规范性文件做好衔接已有专门规范性文件规定的内容不再重复GCP中留好接口伦理审查部分主要保留药物临床试验相关特定要求。三是指导原则层面重点做好与ICH E6R3衔接保持基本原则和要求一致E6R3中遵循当地监管要求的进一步明确监管要求我国相关规范性文件要求高于E6R3的以我国监管要求为准E6R3中属于建议性做法和理念性描述而非监管刚性要求的不纳入我国GCP而是采取全文实施E6R3方式为临床试验实施灵活性预留空间同步启动核查、检查要点修订我国其他技术指导原则要与GCP和E6R3做好衔接根据实际需要开展制、修订工作逐步完善我国GCP制度的技术指导原则体系加强实施指导。
充分吸收2020版GCP的经验。2020版GCP具备较好的工作基础其篇章结构、文字表述以及适应我国实践的特有规定在GCP修订时予以合理保留和借鉴。
(三)注重解决我国临床试验实践中的问题。以问题为导向,针对监管部门和临床试验各参与方反映的问题,注意研究提出解决措施。
三、主要内容
《药物临床试验质量管理规范修订稿征求意见稿以下简称GCP修订稿征求意见稿分为总则、伦理审查委员会、主要研究者和药物临床试验机构、申办者、数据治理、附则6个章节54个条款。
与2020版GCP相比本次修订保留了总则、伦理审查委员会、主要研究者和药物临床试验机构、申办者、附则5个章节增加了数据治理1个章节。考虑到全文实施E6R3中文版只保留我国特有的术语及其定义删除试验方案、研究者手册、必备文件管理3个章节。
四、需要说明的问题
GCP修订稿征求意见稿与ICH E6R3相比重点增删的内容
1.试验参与者表述参考E6R3中文版将“受试者”改为“试验参与者”突出试验参与者在临床试验中的主动性体现对个体权益和主体性的尊重。
2.主要删减内容包括主要研究者与药物临床试验机构章节删除知情同意书包含的细节申办者章节删除监查和稽查的具体要求删除试验方案、研究者手册、必备文件管理3个章节GCP修订稿征求意见稿中保留原则性要求具体操作参照E6R3中文版。
3.总则中保留并强调了利益冲突回避原则增加支持新技术使用原则。伦理审查委员会章节保留2020版GCP审查中应当特别关注的情形以及伦理审查委员会应当受理并妥善处理试验参与者的相关诉求。增加伦理审查委员会文件保存以及提供记录要求。
4.将E6R3中“适用当地监管要求”进行了转化伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定优化安全性信息的报告流程明确相关责任方明确临床试验必备记录保存年限明确生物等效性试验生物样品留样要求等。
GCP修订稿征求意见稿与2020版GCP相比增删的内容
法律法规依据增加了《药品注册管理办法》。
2.增加质量源于设计、风险相称及切合目的的原则性表述,并将三个理念贯穿各章节。
3.增加新技术、新方法在临床试验应用的原则。鼓励使用新技术、新方法支持药物临床试验开展,应符合当前伦理、科学和相关法律法规要求。
4.进一步优化安全性事件报告流程及相应审查要求主要研究者应当立即向申办者和伦理审查委员会报告严重不良事件SAE。可疑且非预期严重不良反应SUSAR和药物研发期间安全性更新报告DSUR由申办者报告主要研究者和伦理审查委员会SUSAR报告的方式与需采取措施的紧迫性和临床试验用药品安全性特征的变化相称。申办者应当将SUSAR、其他潜在的严重安全性风险信息快速报告监管部门对药品监督管理部门提出的风险处理要求、其他潜在的严重安全性风险信息、需要立即关注或采取措施的紧急安全性问题应当快速通知伦理审查委员会和主要研究者。伦理审查委员会应当重点关注并及时审查安全性事件除2020版GCP规定的内容外增加主要研究者报告的SAE、严重持续不依从问题、其他潜在的严重安全性风险信息伦理审查SUSAR和DSUR的方式应遵循风险相称原则。
5.完善伦理审查委员会章节内容。明确伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定,并履行药品监督管理部门提出的监管要求。删除伦理审查委员会组成与运行具体内容,总结凝练为“组成运行要求”一款,修订审查意见类型和内容要求,与《涉及人的生命科学和医学研究伦理审查办法》保持一致。
6.进一步明确临床试验质量最终责任人为申办者,临床试验现场最终责任人为主要研究者。
7.主要研究者与药物临床试验机构章节。根据E6R3术语对研究者的定义研究者是负责实施临床试验的人员单个人如果试验由团队实施其负责人为主要研究者单个人考虑到我国药物临床试验均由团队实施的实际情况将“研究者”修改为“主要研究者”。
8.申办者章节。增加申办者监督职责、盲态保持和风险管理有关内容以及疫苗试验用药品管理和使用要求。在本章节中将实操细节删除按照E6R3中文版执行必要时在核查要点中体现。
9.强调质量管理。申办者采用适合的体系,对临床试验全过程进行质量管理。药物临床试验机构应当建立药物临床试验质量管理体系,并确保其有效运行。实施质量管理应当基于风险,明确了风险识别与评估、风险控制、风险沟通、风险审查和风险报告的要求及主要考虑因素。
10.增加数据治理章节。明确各方对数据治理的职责与要求。
11.附则。除本条款纳入的术语及其定义外本规范涉及的其他术语及其定义参考ICH E6R3中文版术语表。纳入适应我国临床试验实践的术语包括主要研究者、其他潜在的严重安全性风险信息、药物临床试验质量管理以及药物临床试验质量管理体系。
(三)关于药物临床试验必备文件
为指导和规范药物临床试验必备文件的保存国家药监局2020年6月3日发布了《药物临床试验必备文件保存指导原则》。GCP修订过程中将其与E6R3附件1中的《附录C临床试验实施的必备记录》进行了对比对比认为《药物临床试验必备文件保存指导原则》的有关要求均包含在附录C中且附录C对必备文件进行了合理扩展。E6R3中文版原文实施可替代《药物临床试验必备文件保存指导原则》GCP修订稿中将原“必备文件”表述修改为“必备记录”。

View File

@ -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”。

Binary file not shown.

View File

@ -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 its 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 trials 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.
Its 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 youve 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, youll 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, youll 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, its 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 cant 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 cant be mitigated in any other way, its classed as an acceptable risk.
For some specific examples, lets say that a site offers significant benefits to the overall study (for example, a unique participant pool) but its 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, its 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.
Its 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, its 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 its money, time, or therapeutic treatment. A studys 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, its 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 Its 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 trials 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,16 @@
费用发票底稿(医疗、护理、营养、交通等),,,,,,,,预期费用,,,
支出项目,日期,金额,医保金额,自费金额,不予支持金额,,,项目,原由,金额,预估逻辑
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
总计,,,,,,,,,,,
1 费用发票底稿(医疗、护理、营养、交通等) 预期费用
2 支出项目 日期 金额 医保金额 自费金额 不予支持金额 项目 原由 金额 预估逻辑
3
4
5
6
7
8
9
10
11
12
13
14
15
16 总计

Binary file not shown.

View File

@ -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】试算赔偿、补偿金额,与申请人(受试者)沟通方案设计与执行。,,,,,,,,,,,,
1 内部案件编号
2 适用说明 所有理赔申请,在获得基本信息后,均因有此表格进行评估。对于SUSAR应完成表格中的每一项目;对于其他理赔申请,也应参考该表格,以便获得足够的依据,遵循“公正”与责任对等进行后续的协商、谈判。
3 【步骤A.1】材料完整性评估,在本报告定稿时,以下内容必须均有勾选。收集完成包括,并不需要收集。当考虑可能为SUSAR时,按此表格评估。如明确非SUSAR,则跳过此表格评估。
4 A.1 材料完整性:用于评估的材料
5 投保时试验方案 收集完成 最新版本试验方案
6 投保时研究者手册 收集完成 最新版研究者手册
7 已签署的ICF 被保险人与试验机构协议
8 PI签署过的SAE表 申办者评估后SUSAR
9 递交监管的XML ACK文件
10 AE/SAE治疗过程 AE/SAE治疗费用
11 【步骤A.2】对比投保时的方案、IB与受试者入组时的方案、IB,关注两者的差异。是否存在变更,变更的原因。判断依据记录与A.2底稿,以便质控。
12 A.2 方案与IB变更情况:是否与投保时方案发生重大变化 无重大变化
13 入组标准 一般变更 排除标准 一般变更
14 用药计划变化 一般变更 项目执行状态 一般变更
15 安全性列表 无变更
16 结论 当前的试验方案与购买保险时的方案无重大变化, 现行保单有效。
17 【步骤A.3】受试者编号、入组情况,与保单覆盖的受试者数量是否匹配。避免保单覆盖10位患者,但受试者已为第15位入组者,超出保障范围。判断依据记录与A.3底稿,以便质控。
18 A.3 受试者是否在本保单范围
19 本受试者编号 已入组受试者数量
20 结论
21 【步骤A.4】通过受试者入组标准、入组后的治疗过程,判断是否存在严重的方案违背;如出现严重的方案违背,可能导致该伤害不受保险条款保障。判断依据记录与A.4底稿,以便质控。
22 A.4 判断是否存在严重的方案违背 轻微方案违背
23 入组标准 排除标准
24 用药记录与方案
25 结论 该理赔申请中的受试者治疗过程轻微方案违背, 继续评估事件性质。
26 【步骤A.5】检查并确认是否为SUSAR报告,研究者与申办者的判断一致时,直接采纳。如不一致,则审阅其判断原由。采信明确有原由表述的、原由有资料支撑的、从理论与专业性角度更合理的。完全无法评估时,组织3位与试验无关的药物安全专家进行独立评估。
27 A.5 检查是否为SUSAR
28 研究者 申办者 独立评估
29 是否预期
30 是否相关
31 是否严重
32 递交监管及时性 是否受试药物
33 结论
34 【步骤A.6】对相关性进行进一步研究,判断相关程度。研究者判断、同类产品数据(包括国外已上市)、发表的文章,对安全性进行快速检索与评估。与研究者评估与相关资料存在明显差异,或无资料可支持,必要时则可安排独立评估。
35 A.6 事件原因研究
36 研究者 同类产品/文献/机理 独立评估
37 与受试药物的相关性
38 与试验流程的相关性
39 结论
40 【步骤A.7】对上述评估进行总结,做出是否按SUSAR理赔的建议。无论SUSAR或非SUSAR,决定需进行赔偿时,则继续L部分(法律评估)。
41 A.7 综合结论 按主险赔付
42
43 下接【步骤L.1】试算赔偿、补偿金额,与申请人(受试者)沟通方案设计与执行。

View File

@ -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】,,,,,,,,,,,,,,
1 内部案件编号
2
3 临床试验 责任保险事件信息登记
4 【步骤C.1】填表说明:与保司沟通时填写,用于逐步完成信息收集。 接到理赔案件当日时完成;获得准许联系报案人或报案人指定的联系人。
5 保单与理赔申请人【从保司处获得】
6 承保人 保司案件号
7 保单号 投保人
8 保险期限 投保人同意申请理赔
9 报案来源 报案人姓名
10 报案人联系方式
11 信息提供人
12 医院端岗位 (如CRC) 姓名 CRC张
13 联系电话 邮箱
14 申办者端岗位 (如PV团队) 姓名 药物警戒专员
15 联系电话 邮箱
16 【步骤C.2】与指定的医院端信息提供人联系,获得以下信息,并在获得信息后,要求对方邮件发送理赔必须的文件。
17 项目信息
18 临床试验方案全称 项目123 方案编号/版本号
19 产品名称 产品类型
20 研发阶段
21 研究中心名称
22 中心编号 受试者编号 1010
23 受试者姓名缩写 年龄/性别
24 入组日期 退出日期
25 受试者当前状态 曾签署几次ICF
26 事件信息与研究者评价
27 研究者获知事件时间 上报申办者时间
28 是否报告伦理 报告伦理时间
29 事件名称 2025.10.24 骨髓抑制 CTCAE 3 2025.12.3 骨髓抑制 CTCAE 3 事件结局 太和医院:可能相关以上/100% 住院
30 是否方案偏离/违背 研究者对相关性评价 8008:肯定有关
31 收集原始文件的邮件模板: CRC张 您好,关于项目123项目中的受试者1010的保险理赔申请已收悉。请按理赔要求提供以下信息: -最新版本的临床试验方案; -该中心的临床试验合同; -伦理批件; -该患者签署的知情同意书; -研究者签署过的AE/SAE报告; - 受试者发生事件后的治疗记录 -治疗费用票据 请尽快提供相关资料,如无法一次性提供,也可分多次发送。 湖北十堰; 费用:4万7
32 【步骤C.3】与指定的企业端信息提供人联系,获得以下信息,并在获得信息后,要求对方邮件发送理赔必须的文件。
33 事件信息与申办方判断
34 事件名称 申办者相关性评价
35 预期性 严重性
36 是否报告监管 报告监管时间
37 是否揭盲 揭盲结果
38 收集原始文件的邮件模板: 药物警戒专员 您好,关于项目123项目中的受试者1010的保险理赔申请已收悉。请按理赔要求提供以下信息: -最新版本的研究者手册; -或,不良反应列表; -SUSAR报告PDF版本; -递交监管的XML文件(含盲底信息); -ACK文件; -AOSE报告
39 下接信息评估【步骤A.X】

View File

@ -0,0 +1,7 @@
方案与IB变更情况,,,,
,入组时,受试者入组时,差异点,评判
入选标准,,,,
排除标准,,,,
治疗组别,,,,
用药方案,,,,
安全性信息,,,,
1 方案与IB变更情况
2 入组时 受试者入组时 差异点 评判
3 入选标准
4 排除标准
5 治疗组别
6 用药方案
7 安全性信息

166
convert_to_word.py Normal file
View File

@ -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)

14
index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RMO一站式临床试验风险管理</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

3524
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -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"
}
}

BIN
pic/PV Model.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
pic/logo/vDano.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
pic/logo/中再logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
pic/logo/华泰财产.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
pic/logo/平安logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
pic/logo/药盾logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
pic/why risk management.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
pic/上传方案截图.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

BIN
public/pic/PV Model.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
public/pic/logo/vDano.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

125
src/App.tsx Normal file
View File

@ -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 (
<Router>
<QuoteModalProvider>
<Routes>
{/* 免登录浏览区路由 */}
<Route path="/*" element={
<Layout>
<Routes>
<Route path="/" element={<Home />} />
{/* 风险职责(原各方关注)路由 */}
<Route path="/concern" element={<RiskDutiesOverview />} />
<Route path="/sponsor" element={<Sponsor />} />
<Route path="/holder" element={<Holder />} />
<Route path="/institution" element={<Institution />} />
<Route path="/service-provider" element={<ServiceProvider />} />
{/* 风险数据路由 */}
<Route path="/risk-data/smart-acquisition" element={<SmartAcquisition />} />
<Route path="/risk-data/pv-report" element={<PVReport />} />
<Route path="/risk-data/drug-safety-dict" element={<DrugSafetyDict />} />
{/* 风险活动临床试验原RMO模式路由 */}
<Route path="/rmo-mode" element={<RmoMode />} />
<Route path="/rmo-mode/insurance" element={<RmoMode />} />
<Route path="/rmo-mode/guarantee" element={<RmoMode />} />
<Route path="/rmo-mode/insurance-guarantee" element={<RmoMode />} />
{/* 风险活动:上市应用路由 */}
<Route path="/post-market" element={<PostMarket />} />
{/* 海外风险路由 */}
<Route path="/overseas" element={<Overseas />} />
{/* 资源中心(原体系管理)路由,含常见问题 */}
<Route path="/system-management" element={<ResourceCenter />} />
<Route path="/system-management/laws" element={<ResourceCenter />} />
<Route path="/system-management/practice-guide" element={<ResourceCenter />} />
<Route path="/system-management/training" element={<ResourceCenter />} />
<Route path="/system-management/faq" element={<FAQ />} />
{/* 常见问题保留原路径以兼容 */}
<Route path="/faq" element={<FAQ />} />
{/* 其他路由 */}
<Route path="/participant" element={<Participant />} />
<Route path="/login" element={<Login />} />
</Routes>
</Layout>
} />
{/* 登录后系统路由(需权限验证) */}
<Route path="/dashboard/*" element={
<ProtectedRoute>
<DashboardLayout>
<Routes>
<Route index element={<Dashboard />} />
{/* 投保人可见 */}
<Route path="project-quotes" element={<ProjectQuotes />} />
<Route path="projects" element={<ProjectList />} />
<Route path="projects/:id" element={<ProjectDetail />} />
{/* 保险人可见 */}
<Route path="inquiries" element={<InquiryList />} />
<Route path="inquiries/:id" element={<InquiryDetail />} />
{/* 投保人、保险人可见 */}
<Route path="claims" element={<ClaimProgress />} />
<Route path="claims/:id" element={<ClaimDetail />} />
{/* 投保人可见 */}
<Route path="tools" element={<Tools />} />
<Route path="tools/premium-calculator" element={<PremiumCalculator />} />
<Route path="tools/icf-editor" element={<ICFEditor />} />
<Route path="tools/risk-scoring" element={<RiskScoring />} />
<Route path="tools/protocol-risk" element={<ProtocolRiskAssessment />} />
<Route path="tools/drug-safety" element={<DrugSafetyQuery />} />
</Routes>
</DashboardLayout>
</ProtectedRoute>
} />
</Routes>
<QuoteRequestModal />
</QuoteModalProvider>
</Router>
)
}
export default App

View File

@ -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;
}
}

View File

@ -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 (
<div className="dashboard-layout">
{/* 侧边栏 */}
<aside className={`dashboard-sidebar ${sidebarOpen ? 'open' : 'closed'}`}>
<div className="sidebar-header">
<Link to="/dashboard" className="sidebar-logo">
<h2>RMO</h2>
<span></span>
</Link>
<button
className="sidebar-toggle"
onClick={() => setSidebarOpen(!sidebarOpen)}
aria-label="切换侧边栏"
>
{sidebarOpen ? '◀' : '▶'}
</button>
</div>
<nav className="sidebar-nav">
<Link
to="/dashboard"
className={`nav-item ${isActive('/dashboard') && !isActiveParentMulti(['/dashboard/projects', '/dashboard/project-quotes', '/dashboard/inquiries', '/dashboard/claims', '/dashboard/tools']) ? 'active' : ''}`}
>
<span className="nav-icon">📊</span>
{sidebarOpen && <span className="nav-text"></span>}
</Link>
{/* 投保人可见:项目报价、项目列表 */}
{isPolicyholder && (
<>
<Link
to="/dashboard/project-quotes"
className={`nav-item ${isActiveParent('/dashboard/project-quotes') ? 'active' : ''}`}
>
<span className="nav-icon">💰</span>
{sidebarOpen && <span className="nav-text"></span>}
</Link>
<Link
to="/dashboard/projects"
className={`nav-item ${isActiveParent('/dashboard/projects') ? 'active' : ''}`}
>
<span className="nav-icon">📋</span>
{sidebarOpen && <span className="nav-text"></span>}
</Link>
</>
)}
{/* 保险人可见:询价列表 */}
{isInsurer && (
<Link
to="/dashboard/inquiries"
className={`nav-item ${isActiveParent('/dashboard/inquiries') ? 'active' : ''}`}
>
<span className="nav-icon">💼</span>
{sidebarOpen && <span className="nav-text"></span>}
</Link>
)}
{/* 投保人、保险人可见:理赔进度 */}
{(isPolicyholder || isInsurer) && (
<Link
to="/dashboard/claims"
className={`nav-item ${isActiveParent('/dashboard/claims') ? 'active' : ''}`}
>
<span className="nav-icon">📝</span>
{sidebarOpen && <span className="nav-text"></span>}
</Link>
)}
{/* 智能工具:所有登录用户可见 */}
<div className="nav-group">
<div className={`nav-item ${isActiveParentMulti(['/dashboard/tools']) ? 'active' : ''}`}>
<span className="nav-icon">🛠</span>
{sidebarOpen && <span className="nav-text"></span>}
</div>
{isActiveParentMulti(['/dashboard/tools']) && (
<div className="nav-submenu">
{/* 保费测算工具:所有登录用户可见 */}
<Link
to="/dashboard/tools/premium-calculator"
className={`nav-subitem ${isActive('/dashboard/tools/premium-calculator') ? 'active' : ''}`}
>
</Link>
{/* ICF智能修改仅投保人可见 */}
{isPolicyholder && (
<Link
to="/dashboard/tools/icf-editor"
className={`nav-subitem ${isActive('/dashboard/tools/icf-editor') ? 'active' : ''}`}
>
ICF智能修改
</Link>
)}
{/* 方案风险评分:仅投保人可见 */}
{isPolicyholder && (
<Link
to="/dashboard/tools/risk-scoring"
className={`nav-subitem ${isActive('/dashboard/tools/risk-scoring') ? 'active' : ''}`}
>
</Link>
)}
{/* 方案风险评估:所有登录用户可见 */}
<Link
to="/dashboard/tools/protocol-risk"
className={`nav-subitem ${isActive('/dashboard/tools/protocol-risk') ? 'active' : ''}`}
>
</Link>
{/* 药安查:所有登录用户可见 */}
<Link
to="/dashboard/tools/drug-safety"
className={`nav-subitem ${isActive('/dashboard/tools/drug-safety') ? 'active' : ''}`}
>
</Link>
</div>
)}
</div>
</nav>
</aside>
{/* 主内容区 */}
<div className="dashboard-main">
{/* 顶部栏 */}
<header className="dashboard-header">
<div className="header-left">
<Link to="/" className="header-logo">
<h2>RMO</h2>
<span></span>
</Link>
<nav className="header-nav">
<Link to="/" className={isActive('/') ? 'active' : ''}>
</Link>
{/* 风险职责 */}
<div
className="nav-dropdown"
onMouseEnter={() => setConcernMenuOpen(true)}
onMouseLeave={() => setConcernMenuOpen(false)}
>
<Link
to="/concern"
className={isActiveParentMulti(['/concern', '/sponsor', '/holder', '/institution', '/service-provider', '/participant']) ? 'active' : ''}
>
</Link>
{concernMenuOpen && (
<div className="dropdown-menu">
<Link to="/sponsor"></Link>
<Link to="/holder"></Link>
<Link to="/participant"></Link>
<Link to="/institution"></Link>
<Link to="/service-provider">CXO职责</Link>
</div>
)}
</div>
{/* 临床试验 */}
<div
className="nav-dropdown"
onMouseEnter={() => setRmoMenuOpen(true)}
onMouseLeave={() => setRmoMenuOpen(false)}
>
<Link
to="/rmo-mode"
className={isActiveParentMulti(['/rmo-mode', '/insurance', '/guarantee', '/insurance-guarantee']) ? 'active' : ''}
>
</Link>
{rmoMenuOpen && (
<div className="dropdown-menu">
<Link to="/rmo-mode/insurance"></Link>
<Link to="/rmo-mode/guarantee"></Link>
<Link to="/rmo-mode/insurance-guarantee"></Link>
</div>
)}
</div>
{/* 上市应用 */}
<Link to="/post-market" className={isActive('/post-market') ? 'active' : ''}>
</Link>
{/* 海外风险 */}
<Link to="/overseas" className={isActive('/overseas') ? 'active' : ''}>
</Link>
{/* 资源中心 */}
<div
className="nav-dropdown"
onMouseEnter={() => setSystemMenuOpen(true)}
onMouseLeave={() => setSystemMenuOpen(false)}
>
<Link
to="/system-management"
className={isActiveParentMulti(['/system-management', '/faq']) ? 'active' : ''}
>
</Link>
{systemMenuOpen && (
<div className="dropdown-menu">
<Link to="/system-management/laws"></Link>
<Link to="/system-management/practice-guide"></Link>
<Link to="/system-management/training"></Link>
<Link to="/system-management/faq"></Link>
</div>
)}
</div>
</nav>
</div>
<div className="header-right">
<div className="user-info">
<span className="user-name">{user?.name}</span>
<span className="user-role">{user?.role}</span>
</div>
<button onClick={handleLogout} className="logout-btn">
退
</button>
</div>
</header>
{/* 页面内容 */}
<main className="dashboard-content">
{children}
</main>
</div>
</div>
)
}
export default DashboardLayout

42
src/components/Footer.css Normal file
View File

@ -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;
}
}

32
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,32 @@
import './Footer.css'
function Footer() {
return (
<footer className="footer">
<div className="container">
<div className="footer-content">
<div className="footer-section">
<h3></h3>
<p></p>
</div>
<div className="footer-section">
<h3></h3>
<p>400-XXX-XXXX</p>
<p>info@rmo.com</p>
</div>
<div className="footer-section">
<h3></h3>
<p></p>
<p></p>
</div>
</div>
<div className="footer-bottom">
<p>&copy; 2025 RMO一站式临床试验风险管理. All rights reserved.</p>
</div>
</div>
</footer>
)
}
export default Footer

337
src/components/Header.css Normal file
View File

@ -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;
}
}

159
src/components/Header.tsx Normal file
View File

@ -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 (
<header className="header">
<div className="container">
<div className="header-content">
<Link to="/" className="logo">
<h1>RMO</h1>
<span></span>
</Link>
<nav className="nav nav-level1">
<Link to="/" className={isActive('/') && location.pathname === '/' ? 'active' : ''}>
</Link>
{/* 一级:风险职责(二级菜单) */}
<div
className="nav-dropdown"
onMouseEnter={() => setConcernOpen(true)}
onMouseLeave={() => setConcernOpen(false)}
>
<Link
to="/concern"
className={isActiveParent(['/concern', '/sponsor', '/holder', '/institution', '/service-provider', '/participant']) ? 'active' : ''}
>
<span className="dropdown-arrow"></span>
</Link>
{concernOpen && (
<div className="dropdown-menu dropdown-menu-level2">
<Link to="/sponsor"></Link>
<Link to="/holder"></Link>
<Link to="/participant"></Link>
<Link to="/institution"></Link>
<Link to="/service-provider">CXO职责</Link>
</div>
)}
</div>
{/* 一级:风险数据(二级菜单) */}
<div
className="nav-dropdown"
onMouseEnter={() => setRiskDataOpen(true)}
onMouseLeave={() => setRiskDataOpen(false)}
>
<Link
to="/risk-data/smart-acquisition"
className={isActiveParent(['/risk-data']) ? 'active' : ''}
>
<span className="dropdown-arrow"></span>
</Link>
{riskDataOpen && (
<div className="dropdown-menu dropdown-menu-level2">
<Link to="/risk-data/smart-acquisition"></Link>
<Link to="/risk-data/pv-report">PV报告</Link>
<Link to="/risk-data/drug-safety-dict"></Link>
</div>
)}
</div>
{/* 一级:风险活动(二级+三级菜单:临床试验下含保险方案等,上市应用) */}
<div
className="nav-dropdown nav-dropdown-has-level3"
onMouseEnter={() => setRiskActivitiesOpen(true)}
onMouseLeave={() => setRiskActivitiesOpen(false)}
>
<span
className={`nav-dropdown-trigger ${isActiveParent(['/rmo-mode', '/post-market']) ? 'active' : ''}`}
>
<span className="dropdown-arrow"></span>
</span>
{riskActivitiesOpen && (
<div className="dropdown-menu dropdown-menu-level2 dropdown-menu-level3-wrapper">
<div className="dropdown-submenu">
<Link to="/rmo-mode/insurance"></Link>
<Link to="/rmo-mode/guarantee"></Link>
<Link to="/rmo-mode/insurance-guarantee"></Link>
</div>
<div className="dropdown-submenu">
<Link to="/post-market" className="dropdown-submenu-title-link"></Link>
</div>
</div>
)}
</div>
{/* 一级:海外风险 */}
<Link to="/overseas" className={isActive('/overseas') ? 'active' : ''}>
</Link>
{/* 一级:资源中心(二级菜单) */}
<div
className="nav-dropdown"
onMouseEnter={() => setResourceOpen(true)}
onMouseLeave={() => setResourceOpen(false)}
>
<Link
to="/system-management"
className={isActiveParent(['/system-management', '/faq', '/system-management/laws']) ? 'active' : ''}
>
<span className="dropdown-arrow"></span>
</Link>
{resourceOpen && (
<div className="dropdown-menu dropdown-menu-level2">
<Link to="/system-management/laws"></Link>
<Link to="/system-management/practice-guide"></Link>
<Link to="/system-management/training"></Link>
<Link to="/system-management/faq"></Link>
</div>
)}
</div>
{/* 登录/用户信息 */}
{isAuthenticated ? (
<div className="user-menu">
<Link to="/dashboard" className="user-info-link">
<span className="user-name">{user?.name}</span>
<span className="user-role">{user?.role}</span>
</Link>
<button onClick={handleLogout} className="logout-btn-header">
退
</button>
</div>
) : (
<Link to="/login" className="login-btn">
</Link>
)}
</nav>
</div>
</div>
</header>
)
}
export default Header

11
src/components/Layout.css Normal file
View File

@ -0,0 +1,11 @@
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding-top: 56px;
}

23
src/components/Layout.tsx Normal file
View File

@ -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 (
<div className="layout">
<Header />
<main className="main-content">
{children}
</main>
<Footer />
</div>
)
}
export default Layout

View File

@ -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 (
<div
className={`page-container${compact ? ' page-container--compact' : ''}`}
>
{children}
</div>
)
}
export default PageContainer

View File

@ -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 (
<div className={headerClass}>
<div className="header-content">
<div className="header-left">
<h2 className="page-title">{title}</h2>
{description && <p className="page-description">{description}</p>}
</div>
{actions && <div className="header-actions">{actions}</div>}
</div>
</div>
)
}
export default PageHeader

View File

@ -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 (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh'
}}>
<div>...</div>
</div>
)
}
if (!isAuthenticated) {
// 保存当前路径,登录后可以跳转回来
return <Navigate to="/login" state={{ from: location }} replace />
}
return <>{children}</>
}
export default ProtectedRoute

View File

@ -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;
}
}

View File

@ -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<QuoteFormData>(initialForm)
const [uploading, setUploading] = useState(false)
const [aiQuote, setAiQuote] = useState<string | null>(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<HTMLInputElement>) => {
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` +
'· 建议每人保额80120 万\n· 每次事故限额400600 万\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 (
<div className="quote-modal-overlay" onClick={handleClose}>
<div className="quote-modal" onClick={(e) => e.stopPropagation()}>
<div className="quote-modal-header">
<h2 className="quote-modal-title"></h2>
<button type="button" className="quote-modal-close" onClick={handleClose} aria-label="关闭">
×
</button>
</div>
<div className="quote-modal-body">
<p className="quote-modal-desc"></p>
{/* 第一步:资料 */}
<section className="quote-section">
<h3 className="quote-section-title">1. </h3>
<div className="quote-fill-mode">
<label className="quote-radio">
<input
type="radio"
name="fillMode"
checked={fillMode === 'manual'}
onChange={() => setFillMode('manual')}
/>
<span></span>
</label>
<label className="quote-radio">
<input
type="radio"
name="fillMode"
checked={fillMode === 'upload'}
onChange={() => setFillMode('upload')}
/>
<span>AI </span>
</label>
</div>
{fillMode === 'upload' && (
<div className="form-group">
<label></label>
<input type="file" accept=".pdf,.doc,.docx" onChange={handleUploadChange} />
{uploading && <p className="form-hint">AI </p>}
</div>
)}
<div className="quote-form-grid">
<div className="form-group">
<label></label>
<input
type="text"
value={formData.projectCode}
onChange={(e) => setFormData((d) => ({ ...d, projectCode: e.target.value }))}
placeholder="如CT-2025-001"
/>
</div>
<div className="form-group">
<label></label>
<input
type="text"
value={formData.projectTitle}
onChange={(e) => setFormData((d) => ({ ...d, projectTitle: e.target.value }))}
placeholder="试验方案标题"
/>
</div>
<div className="form-group">
<label></label>
<input
type="text"
value={formData.sponsor}
onChange={(e) => setFormData((d) => ({ ...d, sponsor: e.target.value }))}
placeholder="申办者名称"
/>
</div>
<div className="form-group">
<label></label>
<input
type="text"
value={formData.projectPhase}
onChange={(e) => setFormData((d) => ({ ...d, projectPhase: e.target.value }))}
placeholder="如I期、II期、III期"
/>
</div>
</div>
</section>
{/* 第二步:生成报价 */}
<section className="quote-section">
<h3 className="quote-section-title">2. </h3>
<button
type="button"
className="btn btn-primary"
disabled={!canGenerateQuote || generatingQuote}
onClick={handleGenerateQuote}
>
{generatingQuote ? 'AI 生成中…' : '生成报价'}
</button>
{aiQuote && (
<div className="quote-ai-result">
<pre>{aiQuote}</pre>
</div>
)}
</section>
{/* 第三步:获取精准报价 */}
<section className="quote-section">
<h3 className="quote-section-title">3. </h3>
<p className="quote-section-desc"></p>
<button
type="button"
className="btn btn-primary"
disabled={!aiQuote || sendingPrecise}
onClick={handleGetPreciseQuote}
>
{sendingPrecise ? '发送中…' : preciseSent ? '已发送至各保司' : '获取精准报价'}
</button>
{preciseSent && (
<div className="quote-precise-tip">
<p> rmo@vdano.com</p>
{isAuthenticated && (
<button
type="button"
className="btn btn-secondary btn-sm"
onClick={() => {
handleClose()
navigate('/dashboard/project-quotes')
}}
>
</button>
)}
</div>
)}
</section>
</div>
</div>
</div>
)
}
export default QuoteRequestModal

View File

@ -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<void>
logout: () => void
loading: boolean
}
// 创建 Context
const AuthContext = createContext<AuthContextType | undefined>(undefined)
// AuthProvider 组件
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [token, setToken] = useState<string | null>(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<string, { user: User; token: string }> = {
'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 <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
// 使用 AuthContext 的 Hook
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}

View File

@ -0,0 +1,30 @@
import { createContext, useContext, useState, ReactNode, useCallback } from 'react'
interface QuoteModalContextType {
isOpen: boolean
openQuoteModal: () => void
closeQuoteModal: () => void
}
const QuoteModalContext = createContext<QuoteModalContextType | undefined>(undefined)
export function QuoteModalProvider({ children }: { children: ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
const openQuoteModal = useCallback(() => setIsOpen(true), [])
const closeQuoteModal = useCallback(() => setIsOpen(false), [])
return (
<QuoteModalContext.Provider value={{ isOpen, openQuoteModal, closeQuoteModal }}>
{children}
</QuoteModalContext.Provider>
)
}
export function useQuoteModal() {
const context = useContext(QuoteModalContext)
if (context === undefined) {
throw new Error('useQuoteModal must be used within a QuoteModalProvider')
}
return context
}

143
src/index.css Normal file
View File

@ -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;
}
}

15
src/main.tsx Normal file
View File

@ -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(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>,
)

147
src/pages/Company.css Normal file
View File

@ -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;
}
}

130
src/pages/Company.tsx Normal file
View File

@ -0,0 +1,130 @@
import './Company.css'
function Company() {
return (
<div className="company">
<section className="section hero-section">
<div className="container">
<h1 className="section-title"></h1>
<p className="section-subtitle"></p>
</div>
</section>
<section className="section">
<div className="container">
<div className="company-intro card">
<h2>RMO</h2>
<p>
RMORisk Management Organization
"临研安"
"药盾"
</p>
<p>
RMO将为您启动
RMO服务的对外窗口
RMO组织内的各方为您提供高质量的风险管理服务
</p>
<h3></h3>
<ul className="advantage-list">
<li> <strong></strong>RMO服务的对外窗口</li>
<li> <strong></strong>30</li>
<li> <strong></strong></li>
<li> <strong></strong></li>
</ul>
<h3></h3>
<ul className="advantage-list">
<li> </li>
<li> </li>
<li> 30</li>
<li> 24</li>
<li> </li>
<li> </li>
</ul>
<h3></h3>
<p>
/
</p>
<ul className="advantage-list">
<li>📧 <a href="mailto:rmo@vdanno.com">rmo@vdanno.com</a></li>
<li>📞 <a href="tel:4009606520">4009 606 520</a></li>
</ul>
<p>
</p>
</div>
</div>
</section>
<section className="section activity-section">
<div className="container">
<h2 className="section-title"></h2>
<div className="activity-grid">
<div className="card activity-card">
<div className="activity-date">2024-12</div>
<h3></h3>
<p>RMO模式实践经验</p>
</div>
<div className="card activity-card">
<div className="activity-date">2024-11</div>
<h3></h3>
<p>RMO一站式风险管理解决方案</p>
</div>
<div className="card activity-card">
<div className="activity-date">2024-10</div>
<h3></h3>
<p></p>
</div>
<div className="card activity-card">
<div className="activity-date">2024-09</div>
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
<section className="section resource-section">
<div className="container">
<h2 className="section-title"></h2>
<div className="resource-tabs">
<div className="resource-category">
<h3>📋 </h3>
<ul className="resource-list">
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div className="resource-category">
<h3>📊 </h3>
<ul className="resource-list">
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div className="resource-category">
<h3>🤝 </h3>
<ul className="resource-list">
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div className="resource-category">
<h3>🎥 </h3>
<ul className="resource-list">
<li>RMO模式介绍视频</li>
<li></li>
<li></li>
</ul>
</div>
</div>
</div>
</section>
</div>
)
}
export default Company

View File

@ -0,0 +1,28 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './RiskData.css'
function DrugSafetyDict() {
return (
<PageContainer>
<div className="risk-data-page">
<PageHeader
variant="module"
title="药安字典"
description="药品安全相关术语与数据字典"
/>
<div className="module-content">
<section className="section">
<div className="container">
<div className="card main-card">
<p className="building-notice"></p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default DrugSafetyDict

38
src/pages/FAQ.css Normal file
View File

@ -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;
}

73
src/pages/FAQ.tsx Normal file
View File

@ -0,0 +1,73 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './FAQ.css'
function FAQ() {
return (
<PageContainer>
<div className="faq">
<PageHeader
title="常见问题"
description="FAQ列表与问题解答"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="card main-card">
<h2></h2>
<div className="faq-list">
<div className="faq-item">
<h3>Q: 保险保障范围包括哪些</h3>
<p>A: 保险主要保障SUSARSAE等首要风险</p>
</div>
<div className="faq-item">
<h3>Q: 理赔申请需要多长时间</h3>
<p>A: 正常情况下7-105</p>
</div>
<div className="faq-item">
<h3>Q: 如何申请理赔</h3>
<p>A: 您可以通过医院端CRO报案</p>
</div>
</div>
<h2></h2>
<div className="faq-list">
<div className="faq-item">
<h3>Q: 专项风险管理基金如何设立</h3>
<p>A: 申办者根据项目大小3-5使</p>
</div>
<div className="faq-item">
<h3>Q: 风险减量服务包括哪些内容</h3>
<p>A: 包括风险点检查</p>
</div>
<div className="faq-item">
<h3>Q: 外溢风险管理服务的响应时间是多少</h3>
<p>A: 正常情况下24小时内与医院</p>
</div>
</div>
<h2></h2>
<div className="faq-list">
<div className="faq-item">
<h3>Q: 如何联系RMO服务团队</h3>
<p>A: 除日常的工作群/ rmo@vdanno.com 4009 606 520 </p>
</div>
<div className="faq-item">
<h3>Q: 服务响应时效如何</h3>
<p>A: 日常咨询24小时内回复</p>
</div>
<div className="faq-item">
<h3>Q: 如何跟踪案件处理进展</h3>
<p>A: 您可以通过沟通群或电话咨询案件处理进展</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default FAQ

49
src/pages/Holder.css Normal file
View File

@ -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;
}

63
src/pages/Holder.tsx Normal file
View File

@ -0,0 +1,63 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Holder.css'
function Holder() {
return (
<PageContainer>
<div className="holder">
<PageHeader
title="持有人职责"
description="负责上市后药物安全"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<ul className="responsibility-overview">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card main-card">
<h2></h2>
<div className="content-section">
<h3></h3>
<p>
MAH
使
</p>
<h3></h3>
<ul>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
<h3>RMO模式如何支持持有人</h3>
<ul>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default Holder

438
src/pages/Home.css Normal file
View File

@ -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;
}
}

157
src/pages/Home.tsx Normal file
View File

@ -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 (
<PageContainer>
<div className="home">
{/* Hero Section */}
<section className="hero hero-compact">
<div className="container">
<div className="hero-content">
<h1 className="hero-title"></h1>
<div className="hero-buttons">
<Link to="/rmo-mode/insurance" className="btn"></Link>
<Link to="/rmo-mode/guarantee" className="btn btn-secondary"></Link>
<button type="button" className="btn btn-quote" onClick={openQuoteModal}>
</button>
</div>
</div>
</div>
</section>
{/* 智能工具快捷入口 - 页面中部左侧 */}
<div className="home-quick-tools">
<Link to="/dashboard/project-quotes" className="home-quick-tool-icon" title="一键获取全部保司报价(登录后使用)">
<span className="quick-tool-emoji">📋</span>
<span className="quick-tool-label"></span>
</Link>
<Link to="/dashboard/tools/protocol-risk" className="home-quick-tool-icon" title="方案风险评估(登录后使用)">
<img src="/pic/上传方案截图.png" alt="上传方案进行评估" />
<span className="quick-tool-label"></span>
</Link>
<Link to="/dashboard/tools/drug-safety" className="home-quick-tool-icon" title="药安查(登录后使用)">
<span className="quick-tool-emoji">🔍</span>
<span className="quick-tool-label"></span>
</Link>
</div>
{/* 风险管理逻辑区域 */}
<section className="section logic-section">
<div className="container">
<h2 className="section-title"></h2>
<div className="logic-content">
{/* 基于数据,发现分析 */}
<div className="logic-card">
<div className="logic-header">
<div className="logic-icon">📈</div>
<h3></h3>
</div>
<div className="logic-body">
<p></p>
<ul className="logic-list">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
{/* 管理行动 */}
<div className="logic-card">
<div className="logic-header">
<div className="logic-icon"></div>
<h3></h3>
</div>
<div className="logic-body">
<p></p>
<ul className="logic-list">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
</div>
</div>
</section>
{/* 风险管理体系区域 */}
<section className="section model-section">
<div className="container">
<h2 className="section-title"></h2>
<div className="model-diagram">
<Link to="/system-management/laws" className="model-card">
<div className="model-icon">📜</div>
<h3></h3>
<p></p>
</Link>
<div className="model-arrow"></div>
<div className="model-card">
<div className="model-icon">📚</div>
<h3></h3>
<p></p>
</div>
<div className="model-arrow"></div>
<div className="model-card">
<div className="model-icon">📊</div>
<h3></h3>
<p></p>
</div>
<div className="model-arrow"></div>
<div className="model-card">
<div className="model-icon">🔍</div>
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
{/* 快速导航 */}
<section className="section nav-section">
<div className="container">
<h2 className="section-title"></h2>
<div className="nav-grid">
<Link to="/sponsor" className="nav-card">
<div className="nav-icon">💼</div>
<h3></h3>
<p></p>
</Link>
<Link to="/holder" className="nav-card">
<div className="nav-icon">📋</div>
<h3></h3>
<p></p>
</Link>
<Link to="/institution" className="nav-card">
<div className="nav-icon">🏥</div>
<h3></h3>
<p></p>
</Link>
<Link to="/participant" className="nav-card">
<div className="nav-icon">👤</div>
<h3></h3>
<p></p>
</Link>
<Link to="/service-provider" className="nav-card">
<div className="nav-icon">🤝</div>
<h3>CXO职责</h3>
<p>CROCDMOSMO支持服务</p>
</Link>
</div>
</div>
</section>
</div>
</PageContainer>
)
}
export default Home

125
src/pages/Institution.css Normal file
View File

@ -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;
}
}

92
src/pages/Institution.tsx Normal file
View File

@ -0,0 +1,92 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Institution.css'
function Institution() {
return (
<PageContainer>
<div className="institution">
<PageHeader
title="研究中心"
description="为试验机构、研究者、伦理委员会提供专业支持"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<ul className="responsibility-overview">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card main-card">
<h2></h2>
<div className="content-section">
<h3></h3>
<ul>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
<h3></h3>
<p>
RMO模式的风险管理服务
</p>
</div>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<div className="content-section">
<h3></h3>
<ul>
<li></li>
<li></li>
<li></li>
<li>SUSAR</li>
<li></li>
</ul>
</div>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<div className="content-section">
<h3></h3>
<p>
RMO模式为伦理委员会提供专业的风险管理评估支持
</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default Institution

135
src/pages/Login.css Normal file
View File

@ -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;
}

106
src/pages/Login.tsx Normal file
View File

@ -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 (
<PageContainer>
<div className="login">
<PageHeader
title="登录"
description="登录后将根据角色身份进入保险报价与理赔页面"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="login-card card main-card">
<h2></h2>
<form className="login-form" onSubmit={handleSubmit}>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<label htmlFor="username">/</label>
<input
type="text"
id="username"
name="username"
placeholder="请输入用户名或邮箱"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
disabled={loading}
/>
<div className="form-hint">
<small>admin, insurer, broker, tpa, patient, institution, cxo123456</small>
</div>
</div>
<div className="form-group">
<label htmlFor="password"></label>
<input
type="password"
id="password"
name="password"
placeholder="请输入密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
/>
</div>
<div className="form-options">
<label className="checkbox-label">
<input type="checkbox" />
<span></span>
</label>
<a href="#" className="forgot-link"></a>
</div>
<button type="submit" className="btn btn-primary" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
<div className="login-footer">
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default Login

40
src/pages/Overseas.css Normal file
View File

@ -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;
}

28
src/pages/Overseas.tsx Normal file
View File

@ -0,0 +1,28 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Overseas.css'
function Overseas() {
return (
<PageContainer>
<div className="overseas module-dashboard-container">
<PageHeader
variant="module"
title="海外风险"
description="跨境临床试验与海外市场的风险管理保险与保障"
/>
<div className="module-content">
<section className="section">
<div className="container">
<div className="card main-card">
<p className="building-notice"></p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default Overseas

28
src/pages/PVReport.tsx Normal file
View File

@ -0,0 +1,28 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './RiskData.css'
function PVReport() {
return (
<PageContainer>
<div className="risk-data-page">
<PageHeader
variant="module"
title="PV报告"
description="药物警戒Pharmacovigilance报告与数据分析"
/>
<div className="module-content">
<section className="section">
<div className="container">
<div className="card main-card">
<p className="building-notice"></p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default PVReport

238
src/pages/Participant.css Normal file
View File

@ -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;
}
}

188
src/pages/Participant.tsx Normal file
View File

@ -0,0 +1,188 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Participant.css'
function Participant() {
return (
<PageContainer>
<div className="participant">
<PageHeader
title="受试者专区"
description="了解临床试验,保障您的权益"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<ul className="responsibility-overview">
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card main-card">
<h2></h2>
<div className="content-section">
<h3></h3>
<p>
/
</p>
<h3></h3>
<ul>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
<h3></h3>
<div className="process-flow">
<div className="flow-step">
<div className="flow-number">1</div>
<p></p>
</div>
<div className="flow-arrow"></div>
<div className="flow-step">
<div className="flow-number">2</div>
<p></p>
</div>
<div className="flow-arrow"></div>
<div className="flow-step">
<div className="flow-number">3</div>
<p></p>
</div>
<div className="flow-arrow"></div>
<div className="flow-step">
<div className="flow-number">4</div>
<p>访</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<div className="rights-grid">
<div className="right-item">
<div className="right-icon">📋</div>
<h3></h3>
<p>
</p>
</div>
<div className="right-item">
<div className="right-icon">🛡</div>
<h3></h3>
<p>
</p>
</div>
<div className="right-item">
<div className="right-icon">💰</div>
<h3></h3>
<p>
</p>
</div>
<div className="right-item">
<div className="right-icon">🔄</div>
<h3>退</h3>
<p>
退
</p>
</div>
</div>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="card">
<h2></h2>
<div className="content-section">
<h3>/</h3>
<div className="compensation-principles">
<div className="principle-item">
<h4></h4>
<p>
<strong></strong><br />
<strong></strong>100%<br />
<strong></strong>便
</p>
</div>
<div className="principle-item">
<h4></h4>
<p>
</p>
</div>
<div className="principle-item">
<h4></h4>
<p>
使
</p>
</div>
<div className="principle-item">
<h4></h4>
<p>
</p>
</div>
</div>
<h3></h3>
<div className="relief-process">
<div className="process-item">
<div className="process-icon">1</div>
<h4></h4>
<p>RMO服务方</p>
</div>
<div className="process-item">
<div className="process-icon">2</div>
<h4></h4>
<p>24</p>
</div>
<div className="process-item">
<div className="process-icon">3</div>
<h4></h4>
<p></p>
</div>
<div className="process-item">
<div className="process-icon">4</div>
<h4></h4>
<p></p>
</div>
</div>
<h3></h3>
<div className="contact-info">
<p><strong>线</strong>400-XXX-XXXX24</p>
<p><strong></strong>service@rmo.com</p>
<p><strong></strong>24</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default Participant

40
src/pages/PostMarket.css Normal file
View File

@ -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;
}

28
src/pages/PostMarket.tsx Normal file
View File

@ -0,0 +1,28 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './PostMarket.css'
function PostMarket() {
return (
<PageContainer>
<div className="post-market module-dashboard-container">
<PageHeader
variant="module"
title="上市应用"
description="药品上市后风险管理与药物警戒的保险与保障方案"
/>
<div className="module-content">
<section className="section">
<div className="container">
<div className="card main-card">
<p className="building-notice"></p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default PostMarket

View File

@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show More