first commit
|
|
@ -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?
|
||||||
|
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
# RMO一站式临床试验风险管理网站
|
||||||
|
|
||||||
|
## 项目简介
|
||||||
|
|
||||||
|
RMO(Risk Management Organization)一站式临床试验风险管理网站,旨在介绍RMO模式,清晰展示申办者、研究机构、SMO、CRO在临床试验中各自应承担的职责,说明保险和保证金方式如何保障受试者安全。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **前端框架**: React 18.2.0
|
||||||
|
- **开发语言**: TypeScript
|
||||||
|
- **路由**: React Router DOM 6.20.0
|
||||||
|
- **构建工具**: Vite 5.0.8
|
||||||
|
- **样式**: CSS3 (原生CSS,无UI框架依赖)
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
rmo-website/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── components/ # 公共组件
|
||||||
|
│ │ ├── Header.tsx # 导航头部
|
||||||
|
│ │ ├── Footer.tsx # 页脚
|
||||||
|
│ │ └── Layout.tsx # 布局组件
|
||||||
|
│ ├── pages/ # 页面组件
|
||||||
|
│ │ ├── Home.tsx # 首页
|
||||||
|
│ │ ├── Company.tsx # 公司介绍
|
||||||
|
│ │ ├── Services.tsx # 我们的服务
|
||||||
|
│ │ ├── Sponsor.tsx # 申办方专区
|
||||||
|
│ │ ├── Institution.tsx # 试验机构专区
|
||||||
|
│ │ ├── Participant.tsx # 受试者专区
|
||||||
|
│ │ └── ServiceProvider.tsx # 服务方专区
|
||||||
|
│ ├── App.tsx # 主应用组件
|
||||||
|
│ ├── main.tsx # 入口文件
|
||||||
|
│ └── index.css # 全局样式
|
||||||
|
├── index.html # HTML模板
|
||||||
|
├── package.json # 项目配置
|
||||||
|
├── tsconfig.json # TypeScript配置
|
||||||
|
├── vite.config.ts # Vite配置
|
||||||
|
└── README.md # 项目说明
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 主要页面
|
||||||
|
|
||||||
|
1. **首页**
|
||||||
|
- 一站式风险管理模式图展示
|
||||||
|
- RMO模式/理念/价值介绍
|
||||||
|
- RMO模式服务方介绍
|
||||||
|
- 快速导航到各角色专区
|
||||||
|
|
||||||
|
2. **公司介绍**
|
||||||
|
- 临研安公司简介
|
||||||
|
- 活动动态(研讨会、发布会、展会、培训会)
|
||||||
|
- 资源中心(法律法规、团体标准、行业共识、视频)
|
||||||
|
|
||||||
|
3. **我们的服务**
|
||||||
|
- 自保(专项风险管理基金)
|
||||||
|
- 风险减量服务
|
||||||
|
- 外溢风险管理服务
|
||||||
|
- 保险服务
|
||||||
|
|
||||||
|
4. **申办方专区**
|
||||||
|
- 风险管理体系(风险识别、风险评估、风险管理策略)
|
||||||
|
- RMO模式解决方案
|
||||||
|
- 操作流程
|
||||||
|
|
||||||
|
5. **试验机构专区**
|
||||||
|
- 试验机构关注要点
|
||||||
|
- 研究者支持
|
||||||
|
- 伦理委员会支持
|
||||||
|
|
||||||
|
6. **受试者专区**
|
||||||
|
- 临床试验介绍
|
||||||
|
- 受试者权益
|
||||||
|
- 损害救济
|
||||||
|
|
||||||
|
7. **服务方专区**
|
||||||
|
- CRO支持
|
||||||
|
- CDMO支持
|
||||||
|
- SMO支持
|
||||||
|
|
||||||
|
## 安装和运行
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
- Node.js >= 16.0.0
|
||||||
|
- npm >= 7.0.0 或 yarn >= 1.22.0
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
或
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 开发模式运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
或
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
项目将在 `http://localhost:3000` 启动,浏览器会自动打开。
|
||||||
|
|
||||||
|
### 构建生产版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
或
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
构建产物将输出到 `dist` 目录。
|
||||||
|
|
||||||
|
### 预览生产版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
或
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发说明
|
||||||
|
|
||||||
|
### 添加新页面
|
||||||
|
|
||||||
|
1. 在 `src/pages/` 目录下创建新的页面组件
|
||||||
|
2. 在 `src/App.tsx` 中添加路由配置
|
||||||
|
3. 在 `src/components/Header.tsx` 中添加导航链接
|
||||||
|
|
||||||
|
### 样式规范
|
||||||
|
|
||||||
|
- 使用CSS变量定义主题色和通用样式(见 `src/index.css`)
|
||||||
|
- 每个组件/页面有独立的CSS文件
|
||||||
|
- 遵循响应式设计,支持移动端访问
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
|
||||||
|
- 使用TypeScript进行类型检查
|
||||||
|
- 遵循React Hooks最佳实践
|
||||||
|
- 组件采用函数式组件
|
||||||
|
|
||||||
|
## 浏览器支持
|
||||||
|
|
||||||
|
- Chrome (最新版本)
|
||||||
|
- Firefox (最新版本)
|
||||||
|
- Safari (最新版本)
|
||||||
|
- Edge (最新版本)
|
||||||
|
|
||||||
|
## 项目特点
|
||||||
|
|
||||||
|
- ✅ 响应式设计,支持PC、平板、手机访问
|
||||||
|
- ✅ 现代化UI设计,专业美观
|
||||||
|
- ✅ 清晰的导航结构
|
||||||
|
- ✅ 完整的内容展示
|
||||||
|
- ✅ 快速加载,性能优化
|
||||||
|
- ✅ TypeScript类型安全
|
||||||
|
|
||||||
|
## 待完善功能
|
||||||
|
|
||||||
|
- [ ] 添加实际的一站式风险管理模式图(图片/可视化图表)
|
||||||
|
- [ ] 实现资源中心的文件下载功能
|
||||||
|
- [ ] 添加在线咨询功能
|
||||||
|
- [ ] 实现服务申请表单提交
|
||||||
|
- [ ] 添加数据统计展示
|
||||||
|
- [ ] 实现用户登录系统(如需要)
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目为内部项目,版权归临研安所有。
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题或建议,请联系:
|
||||||
|
- 邮箱:info@rmo.com
|
||||||
|
- 电话:400-XXX-XXXX
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**: 2025年1月
|
||||||
|
|
||||||
|
|
@ -0,0 +1,774 @@
|
||||||
|
第一章 总 则
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第一条【目的】 为保证药物临床试验过程规范,保护试验参与者的权益、安全和福祉,确保数据和结果科学、真实、可靠,根据《中华人民共和国药品管理法》《中华人民共和国疫苗管理法》《中华人民共和国药品管理法实施条例》《药品注册管理办法》,制定本规范。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二条【适用范围】 本规范适用于经国务院药品监督管理部门批准或备案,为申请药品注册而进行的药物临床试验。药物临床试验的相关活动应当遵守本规范。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三条【涵盖范围】 药物临床试验质量管理规范是药物临床试验全过程应当遵循的伦理、科学和质量的标准。药物临床试验全过程包括计划、启动、执行、记录、监督、评估、分析和报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四条【遵循赫尔辛基宣言原则】 药物临床试验应当符合《世界医学大会赫尔辛基宣言》原则及相关伦理要求,试验参与者的权益、安全和福祉是考虑的首要因素,优先于对科学和社会的获益。伦理审查与知情同意是保障试验参与者权益和福祉的重要措施。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五条【临床试验的科学性原则及获益原则】 药物临床试验应当科学合理,权衡试验参与者和社会的预期风险和获益,只有当预期的获益大于风险时,方可实施或者继续临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第六条【试验设计理念、风险相称原则】 临床试验的设计和实施应当纳入质量源于设计的方法,识别试验的关键质量因素及相关风险,并采用与之相称的风险控制措施,保护试验参与者的权益和安全,确保结果可靠。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第七条【临床试验方案】 临床试验方案应当清晰、简明、科学合理、可操作,在获得伦理审查委员会批准后方可执行。临床试验实施过程中,为确保其科学性和伦理性,必要时可调整试验方案,并再次获得伦理审查委员会批准后方可执行。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第八条【研究人员资质原则】 参与临床试验的各方人员应当具有能够承担临床试验工作相应的教育背景、培训经历和实践经验,在临床试验过程中应当遵守试验方案。凡涉及医学判断或临床决策应当由临床医生做出。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第九条【数据记录处理保存原则】 所有临床试验的纸质或电子资料应当被妥善地记录、处理和保存,保证数据的可靠性和可追溯性。应当保护试验参与者的隐私和个人信息的安全,符合我国关于个人信息保护的有关要求。
|
||||||
|
|
||||||
|
用于数据采集、管理、分析的系统和流程应当符合预期目的,并与对试验参与者的风险和所采集数据的重要性相称。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十条【试验用药品原则】 试验用药品的制备应当符合临床试验用药品生产质量管理相关要求。试验用药品的使用和管理应当符合试验方案和相关法律法规要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十一条【质量管理原则】 药物临床试验的质量管理应当贯穿临床试验全过程,以保护试验参与者的权益和安全,确保临床试验结果可靠,并遵守相关法律法规。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十二条【利益冲突回避原则】 药物临床试验各方应当对可能存在的利益冲突采取回避、控制等管理措施,避免对试验参与者保护及试验结果的可靠性产生影响。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十三条【使用新技术原则】 应用新技术、新方法开展药物临床试验,应当符合伦理、科学和相关法律法规要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二章 伦理审查委员会
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十四条【职责】 伦理审查委员会的职责是保护试验参与者的权益、安全和福祉。伦理审查委员会应当对临床试验的科学性和伦理性进行审查,并特别关注弱势试验参与者的保护。伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定,以及药品监督管理部门的监管要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【审查文件清单】 伦理审查委员会审查的文件包括:试验方案、知情同意书、招募试验参与者的方式和信息、提供给试验参与者的其他书面资料、研究者手册、安全性资料、包含试验参与者补偿信息的文件、主要研究者资质证明文件、重要方案偏离报告、总结报告,以及伦理审查委员会履行其职责所需要的其他文件等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【特殊情况下的伦理审查】 伦理审查委员会应当特别关注以下特殊情况,审查对试验参与者的权益、安全和福祉保护是否充分:
|
||||||
|
|
||||||
|
试验参与者在没有预期获益的临床试验中,知情同意是由其法定代理人替代实施。
|
||||||
|
|
||||||
|
试验参与者为无民事行为能力和限制民事行为能力人员。
|
||||||
|
|
||||||
|
涉及未成年人,伦理审查委员会应当审查针对未成年人的知情同意信息,并综合考虑试验参与者的年龄特征、认知成熟度、心理状态以及适用的法规要求。
|
||||||
|
|
||||||
|
试验方案中明确说明紧急情况下试验参与者或者其法定代理人无法在试验前签署知情同意书。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【防范胁迫与免责】 伦理审查委员会应当审查是否存在强迫、利诱等方式影响试验参与者参加临床试验的情形。伦理审查委员会应当审查知情同意书中是否存在要求试验参与者或者其法定代理人放弃合法权益的内容,也不得含有免除主要研究者、临床试验机构、申办者及其代理机构应当承担责任的内容。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【补偿机制】 伦理审查委员会应当确保知情同意书、提供给试验参与者的其他书面资料说明了给试验参与者补偿的信息,包括补偿方式、数额和计划。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【安全性事件审查】 伦理审查委员会应当重点关注并及时审查以下情况:临床试验中发生主要研究者报告的严重不良事件,临床试验实施过程中为消除对试验参与者紧急危害的试验方案的偏离或者修改,严重、持续的不依从问题,增加试验参与者风险或者显著影响临床试验实施的改变,可能对试验参与者的安全或者临床试验的实施产生不利影响的新信息,其他潜在的严重安全性风险信息。
|
||||||
|
|
||||||
|
对申办者报告的可疑且非预期的严重不良反应,以及药物研发期间安全性更新报告,伦理审查委员会审查的方式至少应当与需要采取措施的紧迫性和试验用药品安全性特征的变化相称。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(六)【跟踪审查】 伦理审查委员会应当对正在实施的临床试验定期跟踪审查,审查的频率应当根据试验参与者的风险程度而定,时间间隔不超过12个月。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(七)【审查意见类型和内容】 伦理审查委员会应当在合理的时限内完成临床试验相关资料的审查或者备案流程,并给出明确的书面审查意见。伦理审查委员会的审查意见有:批准、不批准、修改后批准、修改后再审、继续研究、暂停或者终止研究的决定。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(八)【暂停/终止试验】 伦理审查委员会有权暂停、终止未按照相关要求实施,或者试验参与者出现非预期严重损害的临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(九)【受理试验参与者诉求】 伦理审查委员会应当受理并妥善处理试验参与者的相关诉求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十五条【组成运行要求】 伦理审查委员会应当建立伦理审查工作制度、标准操作规程,健全利益冲突管理机制和伦理审查质量控制机制,保证伦理审查过程独立、客观、公正。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十六条【文件保存】 伦理审查委员会应当保留伦理审查的全部记录,包括伦理审查的书面记录、委员信息、递交的文件、会议记录和相关往来记录等。用于申请药品注册的临床试验,所有记录应当至少保存至试验药物被批准上市后5年;未用于申请药品注册的临床试验,应当至少保存至临床试验终止后5年。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十七条【透明化要求】 伦理审查委员会应当向主要研究者和申办者提供相关书面记录,包括伦理审查委员会名称和地址、参与项目审查的伦理审查委员会委员名单、审查的书面意见、符合本规范及相关法律法规的审查声明等。必要时,主要研究者、申办者或者药品监督管理部门可以要求伦理审查委员会提供其标准操作规程。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三章 主要研究者和药物临床试验机构
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十八条【临床试验机构资质要求】 药物临床试验机构开展药物临床试验,应当建立药物临床试验质量管理体系,并确保其有效运行。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第十九条【主要研究者资质及要求】 主要研究者是临床试验现场的最终责任人,对试验参与者权益、安全及临床试验质量负责。主要研究者应当具备的资质和要求包括:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【资质】 具有在临床试验机构的相应执业资格,具备临床试验所需的教育背景、培训经历和实践经验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【要求】 熟悉申办者提供的试验方案、研究者手册、试验药物相关资料信息。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【要求】 熟悉临床试验相关技术指南,并遵守本规范及相关法律法规。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十条【服务外包】 主要研究者和临床试验机构应当建立完整的工作制度以确保其履行临床试验相关职责和功能。如果授权个人或者单位承担临床试验的相关职责和功能,主要研究者和临床试验机构应当确保其具备相应资质,并对其进行适当监督。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十一条【开展试验必要条件】 主要研究者和临床试验机构应当具有完成临床试验所需的必要条件:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【入组试验参与者的时间和能力】 主要研究者在临床试验协议约定的期限内有足够的时间和能力入组足够数量且符合试验方案的试验参与者。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【完成试验的软硬件】 主要研究者具有使用临床试验所需设施的权限,有权支配并监管参与临床试验的研究人员,正确、安全地实施临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十二条【与伦理审查委员会沟通】 主要研究者与伦理审查委员会的沟通包括:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)临床试验实施前,主要研究者应当获得伦理审查委员会批准;未获得伦理审查委员会批准前,不能筛选试验参与者。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)临床试验实施前、过程中和结束后,主要研究者应当按要求向伦理审查委员会报告,并提供伦理审查需要的所有文件。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)主要研究者应当及时执行伦理审查委员会的审查意见。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十三条【遵守方案】 主要研究者应当遵守试验方案。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【方案偏离】 主要研究者应当按照伦理审查委员会批准的试验方案实施临床试验。如偏离试验方案,主要研究者或者其授权的研究人员应当记录和解释,必要时采取适当的纠正和预防措施,并及时向伦理审查委员会报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【紧急危害下的方案偏离】 为消除对试验参与者的紧急危害,在未获得伦理审查委员会同意的情况下,主要研究者修改或者偏离试验方案,应当及时向伦理审查委员会、申办者报告,并说明理由。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【紧急揭盲】 主要研究者应当按照试验方案的要求实施揭盲。如意外破盲或者紧急揭盲,主要研究者应当立即记录,并向申办者书面说明原因。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十四条【提前终止或者暂停临床试验】 临床试验提前终止或者暂停,主要研究者应当及时通知试验参与者,并给予试验参与者适当的治疗和随访。主要研究者、申办者或伦理审查委员会提前终止或暂停临床试验,主要研究者应当立即向申办者、伦理审查委员会中的非发起方报告,同时报告临床试验机构,并提供书面说明。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十五条【医疗照顾】 主要研究者为临床医生或者由其授权的临床医生应当给予试验参与者适合的医疗处理,并承担与临床试验有关的医学决策责任。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十六条【安全性报告】 主要研究者的安全性报告应当符合以下要求:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【不良事件】 主要研究者应当按照试验方案的要求和时限向申办者报告安全性评价所需的不良事件和/或异常检查结果。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【严重不良事件】 除试验方案或者其他文件(如研究者手册)中规定不需立即报告的严重不良事件外,主要研究者应当在获知后立即向申办者和伦理审查委员会书面报告所有严重不良事件,随后应当及时提供详尽、书面的随访报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【死亡事件】 涉及死亡事件报告,如申办者、伦理审查委员会、药品监督管理部门要求提供其他所需要的资料,如尸检报告和最终医学报告等,主要研究者应当在获得后及时提供。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【可疑且非预期严重不良反应(SUSAR)、其他潜在的严重安全性风险信息、药物研发期间安全性更新报告(DSUR)】 主要研究者收到申办者提供的可疑且非预期严重不良反应、其他潜在的严重安全性风险信息、药物研发期间安全性更新报告等安全性信息后应当及时签阅,并应考虑试验参与者的治疗是否进行相应调整,必要时尽早与试验参与者沟通。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十七条【知情同意】 主要研究者实施知情同意应当遵守赫尔辛基宣言的伦理原则,确保试验参与者是自愿参加临床试验,并通过知情同意过程确保其获知充分的信息:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【获得知情同意】 参加临床试验前,主要研究者应当充分告知试验参与者有关临床试验的相关事宜,获得试验参与者的知情同意并记录,使用经伦理审查委员会批准的最新版知情同意书和其他提供给试验参与者的信息。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【获知新信息告知试验参与者】 主要研究者获得可能影响试验参与者继续参加临床试验的新信息时,应当及时告知试验参与者或者其法定代理人,并作相应记录。如有必要,应当再次签署知情同意书。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【不得强迫、利诱】 研究人员不得采用强迫、利诱等不正当的方式影响试验参与者参加或者继续参加临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【知情同意材料通俗易懂】 知情同意书等提供给试验参与者的资料应当采用通俗易懂的语言和表达方式,使试验参与者或者其法定代理人、见证人易于理解。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【试验参与者充分考虑】 签署知情同意书之前,主要研究者或者经其授权的研究人员应当给予试验参与者或者其法定代理人充分的时间和机会了解临床试验的详细情况,并详尽回答试验参与者或者其法定代理人提出的与临床试验相关的问题。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(六)【知情同意签字】 试验参与者或者其法定代理人,以及执行知情同意的研究人员应当在知情同意书上分别签名并注明日期,如非试验参与者本人签署,应当注明关系。病史记录中应当记录试验参与者知情同意的具体时间和人员。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(七)【缺乏阅读能力时的知情同意】 如试验参与者或者其法定代理人缺乏阅读能力,应当有一位公正的见证人见证整个知情同意过程。应当向试验参与者或者其法定代理人、见证人详细说明知情同意书和其他文字资料的内容。如试验参与者或者其法定代理人口头同意参加临床试验,在有能力情况下应当尽量签署知情同意书,见证人还应当在知情同意书上签字并注明日期,以证明试验参与者或者其法定代理人就知情同意书和其他文字资料得到了准确地解释,并理解了相关内容,同意参加临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(八)【获得知情同意副本】 试验参与者或者其法定代理人应当得到已签署姓名和日期的知情同意书原件或者副本和其他提供给试验参与者的资料,以及后续更新版本。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(九)【无民事行为、限制民事行为能力的知情同意】 试验参与者为无民事行为能力人的,应当取得其法定代理人的书面知情同意;试验参与者为限制民事行为能力人的,应当取得本人及其法定代理人的书面知情同意。当法定代理人代表试验参与者知情同意时,应当在试验参与者可理解的范围内告知试验参与者临床试验的相关信息,并尽量让试验参与者亲自签署知情同意书并注明日期。
|
||||||
|
|
||||||
|
未成年人作为试验参与者,应当征得其法定代理人的知情同意并签署知情同意书。当未成年人有能力做出同意参加临床试验的决定时,还应当征得其本人同意,如果未成年试验参与者本人不同意参加临床试验或者中途决定退出临床试验时,即使法定代理人已经同意参加或者愿意继续参加,也应当以未成年试验参与者本人的决定为准;除非在严重或者危及生命疾病的治疗性临床试验中,主要研究者、其法定代理人认为未成年试验参与者如不参加临床试验其生命会受到危害,这时其法定代理人的同意即可使试验参与者参加或者继续参加;在临床试验过程中,未成年试验参与者达到了签署知情同意的条件,则需要由本人签署知情同意之后方可继续实施。
|
||||||
|
|
||||||
|
限制民事行为能力人在恢复民事行为能力后,需要对其再次知情同意,获得其本人的自愿同意继续或是退出相关临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(十)【紧急情况下的知情同意】 紧急情况下,参加临床试验前不能获得试验参与者的知情同意时,其法定代理人可以代表试验参与者知情同意,若其法定代理人也不在场时,试验参与者的入选方式应当在试验方案以及其他文件中清楚表述,并获得伦理审查委员会的书面同意,同时应当尽快得到试验参与者或者其法定代理人可以继续参加临床试验的知情同意。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(十一)【无预期获益的知情同意】 试验参与者参加没有预期获益的临床试验,原则上应当由试验参与者本人签署知情同意。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十八条【药品管理】 主要研究者和临床试验机构对申办者提供的试验用药品负有管理责任。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【专人管理及管理环节】 主要研究者和临床试验机构应当指派具备相应资质的专门人员管理试验用药品,临床试验机构接收、处理、贮存、分发、使用、回收及退还试验用药品应当遵守相应的规定并保存记录。主要研究者应当确保试验用药品按照试验方案使用,并向试验参与者说明试验用药品的正确使用方法。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【生物等效性试验用药品留样】 主要研究者应当对生物等效性试验的临床试验用药品进行随机抽取留样,至少保存留样至药品上市后2年,并制定相应管理制度。临床试验机构可将留存样品委托具备条件的独立的第三方保存,但不得返还申办者或者与其利益相关的第三方。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第二十九条【记录和报告】 临床试验的记录和报告应当符合以下要求:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【监督履职确保数据可靠】 主要研究者应当监督试验现场的数据采集、各研究人员履行其工作职责的情况,确保数据的可靠性。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【临床试验数据记录、修改、溯源、保存】 主要研究者应当确保所有临床试验数据是从临床试验的源记录中获得的,并保证其可靠性和可追溯性。源记录应当具有可归因性、易读性、同时性、原始性、准确性和完整性。源记录的修改应当留痕,不能掩盖初始数据,并记录修改的理由。以患者为试验参与者的临床试验,相关的医疗记录应当载入门诊或者住院病历系统。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【文件保存年限】 主要研究者和临床试验机构应当按照药品监督管理部门的相关管理要求,妥善保存试验文档。用于申请药品注册的临床试验,临床试验必备记录应当至少保存至试验药物被批准上市后5年;未用于申请药品注册的临床试验,必备记录应当至少保存至临床试验终止后5年。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【个人信息保护】 在临床试验数据和试验参与者信息处理过程中应当注意避免非法或者未授权的收集、存储、使用、更正、传输、提供、公开、删除等。临床试验数据的记录、处理和保存应当确保记录和试验参与者信息的安全。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【所有权转移】 必备记录所有权的转移,需符合相关法律法规的要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十条【进展报告】 主要研究者应当提供试验进展报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)主要研究者应当按照伦理审查委员会的要求提交进展报告和临床试验结果摘要。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)临床试验完成后,主要研究者应当及时向临床试验机构报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)主要研究者应当向申办者提供药品监督管理部门所需要的临床试验相关报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十一条【配合监查、稽查和检查】 主要研究者和临床试验机构应当接受申办者组织的监查和稽查,以及药品监督管理部门的检查,并配合提供所需的与临床试验有关的记录。
|
||||||
|
|
||||||
|
第四章 申办者
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十二条【基本考虑】 申办者作为临床试验相关活动的最终责任人,应当把保护试验参与者的权益、安全和福祉以及数据的可靠性作为临床试验的基本考虑。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十三条【试验设计】 申办者在设计临床试验方案时,应基于足够的安全性和有效性数据支持给药途径、给药剂量和持续用药时间。方案设计应当遵循质量源于设计的原则,确保临床试验的科学性、可靠性和可操作性。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十四条【资源、资质和培训】 申办者团队应当具备以下条件:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)申办者应当根据临床试验需要选用有适当资质的人员,建立临床试验的研究和管理团队,指导、监督临床试验全过程。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)申办者应当选用有适当资质的人员参与临床试验,包括试验的设计、实施、流程、信息和数据处理、数据核对、统计分析和撰写试验总结报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)申办者应当配备医学人员,及时解答与临床试验相关的医学问题。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)申办者应当建立有效的沟通渠道,确保临床试验全过程中所有参与人员能够及时沟通,并记录关键沟通内容。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十五条【申办者的盲态维持制度】 在盲法试验中,申办者应当建立工作制度以确保临床试验全过程保持盲态,并预防和识别破盲。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十六条【合同】 申办者委托服务供应商应当符合以下要求:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)申办者可以将其临床试验的部分或者全部工作任务委托或授权给服务供应商,但应当对服务供应商进行监督和管理。受委托方如存在任务转包,应当获得申办者的书面同意。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)临床试验活动开始前,申办者应当与主要研究者、临床试验机构、服务供应商等所有参加临床试验的相关各方签订临床试验合同,明确各方的角色、责任、权利和义务,以及各方应当避免的、可能的利益冲突。必要时,应当更新临床试验合同。临床试验合同应当条款清晰完整,试验经费应当合理,符合市场规律。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)本规范中对申办者的要求,适用于承担申办者相关工作任务的服务供应商。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十七条【主要研究者选择】 申办者负责选择合适的主要研究者和临床试验机构,以满足开展临床试验的需要,应当制定研究者手册并及时更新,向主要研究者和伦理审查委员会提供试验方案和最新的研究者手册。
|
||||||
|
|
||||||
|
【中药民族药研究者手册】 中药民族药研究者手册的内容应当注明组方理论依据、筛选信息、配伍、功能、主治、已有的人用药经验、药材基原和产地等;来源于古代经典名方的中药复方制剂,注明其出处;相关药材及处方等资料。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十八条【实验室选择】 申办者负责选择符合相关规定且具备相应资质的实验室,承担涉及医学判断的样品检测,监督实验室对临床试验中采集标本的管理、检测、运输和储存等全流程的质量管理。禁止实施与伦理审查委员会批准的试验方案无关的生物样品检测(如基因等)。临床试验结束后,剩余标本的继续保存或者将来可能被使用等情况,应当在试验参与者签署的知情同意书中明确说明保存的时间和数据的保密性问题,以及在何种情况下数据和样本可以和其他主要研究者共享等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第三十九条【与伦理审查委员会以及监管机构的沟通】 临床试验开始前,申办者应当向国务院药品监督管理部门提交相关的临床试验资料,并获得临床试验许可或者完成备案。递交的文件资料应当注明版本号及版本日期。申办者应当及时获取伦理审查委员会相关记录并按要求执行伦理审查意见。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十条【申办者监督】 申办者应当对临床试验全过程进行监督,明确工作标准和程序,确保试验过程符合试验方案和其他相关文件,遵守相关法律法规,并遵循伦理标准。申办者监督措施的范围和程度应当符合目的,与临床试验的复杂性和风险相称。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十一条【质量管理】 申办者应当基于风险,采用适合的体系,对临床试验全过程进行质量管理,有效设计和实施临床试验。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十二条【风险管理】 申办者应当对临床试验进行风险管理。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【风险识别与评估】 申办者应当在试验开始前和整个试验过程中识别可能对关键质量因素产生有意义影响的风险,并对风险损害发生的可能性、可被检测到的程度、对试验参与者保护和试验结果可靠性的影响进行评估。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【风险控制】 风险控制应当与风险对试验参与者权益和安全,以及试验结果可靠性影响的重要性相称。当涉及到可能影响试验参与者安全或试验结果可靠性的关键质量因素时,申办者应当预先设定风险控制可接受范围,当超出预设范围限制时,评估是否需要采取措施。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【风险沟通】 申办者应当记录已识别的风险和相应的缓解措施,并与参与采取措施或受此类活动影响的人员沟通。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【风险审查】 申办者应当结合临床试验期间的新知识和经验,定期审查风险控制措施,以确保现行的质量管理活动的有效性和适用性,并根据需要考虑实施额外的风险控制措施。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【风险报告】 申办者应当在临床试验报告中总结并报告重要的质量问题,包括偏离预先设定可接受范围的问题和补救措施。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十三条【质量保证和质量控制】 申办者应当对临床试验实施质量保证和质量控制。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)申办者负责建立、实施和及时更新有关临床试验质量保证和质量控制相关的书面标准操作规程,确保临床试验的实施,以及数据的产生、记录、报告均遵守试验方案,并符合本规范和监管要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【质量保证】 质量保证应当贯穿于临床试验始终,实施基于风险的策略,以识别严重不依从试验方案,以及违反本规范和监管要求的原因,从而采取纠正和预防措施。
|
||||||
|
|
||||||
|
【稽查】 申办者开展稽查活动应当采取与临床试验实施相关风险相称的方式。申办者的稽查独立于常规监查或质量控制职能之外,目的是评估试验管理和实施的流程是否符合试验方案、本规范和监管要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【质量控制】 申办者应当在临床试验实施的每个阶段和环节采用基于风险的方法进行质量控制,以保证过程规范和数据可靠。在临床试验中,监查和数据管理是主要的质量控制活动。申办者应当委派合格的监查员对临床试验实施监查。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十四条【依从性问题】 申办者应当保证临床试验的依从性。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)对主要研究者、临床试验机构、申办者工作人员或服务供应商在临床试验中不遵守试验方案、标准操作规程、本规范及监管要求的情况采取适当措施予以纠正。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)发现对试验参与者的权益和安全,或对试验结果的可靠性产生或可能产生显著影响的不依从问题时,申办者应当及时进行根本原因分析,采取适当且充分的纠正和预防措施,及时书面报告伦理审查委员会。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)发现严重、持续的不依从问题时,申办者应考虑终止主要研究者、临床试验机构或服务供应商继续参加临床试验,及时书面报告伦理审查委员会,并采取措施将对试验参与者和试验结果的可靠性的影响降至最小。如违反试验方案或者本规范的问题严重时,申办者可追究相关人员的责任,并报告药品监督管理部门。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十五条【安全性评估和报告】 申办者应当在药物临床试验期间进行持续的安全性评估,并按照要求和时限进行报告。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【可能影响安全性的问题】 申办者应当审阅并评估现有的安全性信息,将临床试验中发现的新的可能影响试验参与者安全或参与意愿、可能影响临床试验实施、可能改变伦理审查委员会批准意见的问题,及时通知试验参与者、主要研究者、临床试验机构和伦理审查委员会。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【安全性评估】 申办者收到任何来源的安全性相关信息后,均应当立即分析评估,包括严重性、与试验药物的相关性以及是否为预期事件等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【SUSAR和其他潜在的严重安全性风险信息报告】 申办者应当将可疑且非预期严重不良反应、其他潜在的严重安全性风险信息快速报告国家药品监督管理局药品审评中心,快速报告的方式应当符合要求。申办者向主要研究者和伦理审查委员会递交可疑且非预期严重不良反应报告的方式应当与需采取措施的紧迫性和试验用药品安全性特征的变化相称。药品监督管理部门提出的风险处理要求、其他潜在的严重安全性风险信息和需要立即关注或采取措施的紧急安全性问题应当按照快速报告的时限要求报告伦理审查委员会和主要研究者,不得无故延迟。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【DSUR报告】 申办者的药物研发期间安全性更新报告应当包括临床试验风险与获益评估,并通报给主要研究者、伦理审查委员会和国家药品监督管理局药品审评中心。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十六条【对试验参与者和主要研究者的保险/补偿/赔偿】 申办者应当采取适当方式保证可以给予试验参与者和主要研究者补偿或者赔偿。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)申办者应当提供法律上、经济上的保险或者保证用于补偿临床试验相关的损害,并与临床试验的风险性质和风险程度相适应,但不包括主要研究者和临床试验机构自身的过失所致的损害。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)申办者应当承担试验参与者参加临床试验相关损害的诊疗费用,以及相应的补偿。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)申办者和主要研究者应当及时兑付给予试验参与者的补偿或者赔偿。提供补偿的方式方法,应当符合相关法律法规。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)申办者应当免费向试验参与者提供试验用药品,并支付与临床试验相关的医学检测费用。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十七条【试验用药品】 试验用药品的制备、供应和管理应当符合以下要求:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【生产、标签】 申办者应当确保试验用药品在符合临床试验用药品生产质量管理相关要求的条件下生产和放行;试验用药品的标签上应当标明仅用于临床试验、临床试验信息和试验用药品信息;试验用药品在盲法试验中能够保持盲态。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【贮存、运输、有效期、使用方法】 申办者应当明确规定试验用药品的贮存和运输条件、有效期、使用方法等,确保药物在运输和贮存期间不被污染或者变质,并向主要研究者和临床试验机构提供相应纸质说明,同时保留相关记录。试验用药品为疫苗的,其采购、储存、运输、接种还应当符合国家管理规定。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【试验用药品运达机构】 申办者应当在临床试验获得伦理审查委员会批准和国务院药品监督管理部门许可或者备案之后,及时向主要研究者和临床试验机构提供试验用药品。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【申办者试验用药品管理SOP】 申办者应当制定试验用药品的供应管理规程,包括试验用药品的接收、处理、贮存、分发、使用及回收、销毁等制度。从试验参与者处回收以及研究人员未使用的试验用药品应当返还申办者,或者采用由申办者授权的其他方式处置。所有试验用药品的管理过程应当有书面记录,全过程计数准确。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【紧急揭盲机制】 在盲态试验中,应当建立紧急揭盲的规程和机制,以便在紧急医学状态必需揭盲时能够快速识别试验用药品,同时保持其他试验参与者治疗分配的盲态。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(六)【试验用药品留样】 申办者应当采取措施确保临床试验期间试验用药品的稳定性,确保仅在有效期内提供,并保存足够数量的试验用药品样品,留样数量、方式、时限等应当符合相应要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十八条【数据和记录】 申办者应当履行数据治理的责任,确保数据的可靠性、可追溯性和安全性。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【数据处理和计算机化系统】 申办者应当确保临床试验中部署或使用的电子数据管理系统满足计算机化系统的要求;确认主要研究者或临床试验机构使用的计算机化系统满足临床试验的要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【数据记录修改】 申办者不得更改主要研究者或试验参与者录入的数据,除非有正当理由,且应当在修改前获得主要研究者的同意,并进行记录。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【试验数据保密性】 申办者应当使用试验参与者鉴认代码,识别每一位试验参与者的所有临床试验数据。临床试验揭盲后,申办者应当向主要研究者提供盲态试验的试验参与者的治疗信息。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【统计分析】 申办者应当制定符合试验方案的统计分析计划,对统计编程以及数据处理和分析过程实施适当的质量管理并记录。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【记录的保存】 申办者应当根据监管要求保存临床试验相关的必备记录,并书面告知主要研究者、临床试验机构和服务供应商对试验记录保存的要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第四十九条【申办者应当明确试验记录的查阅权限】 申办者应当明确试验记录的查阅权限。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)申办者应当在试验方案或者其他书面合同中明确主要研究者、临床试验机构和服务供应商允许申办者在监查、稽查、伦理审查及药品监督管理部门的检查中,能够直接查阅临床试验相关的源记录。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)申办者应当确认每位试验参与者均以书面形式同意申办者监查、稽查、伦理审查及药品监督管理部门的检查活动,能够直接查阅临床试验相关的原始医学记录。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五十条【报告】 申办者暂停或者提前终止实施中的临床试验或临床试验实施期间申办者发生变化的,应当及时告知主要研究者和临床试验机构、伦理审查委员会和药品监督管理部门,并说明理由。
|
||||||
|
|
||||||
|
申办者应当按照监管要求向药品监督管理部门提交临床试验报告。临床试验报告应当全面、完整、准确反映临床试验结果,临床试验数据应当与源数据一致。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五章 数据治理
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五十一条【数据生命周期】 申办者、主要研究者和临床试验机构应当在各自职责范围内,承担数据治理责任。数据治理贯穿临床试验数据全生命周期,确保准确报告、验证和解释临床试验相关信息。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【数据采集】 申办者、主要研究者和临床试验机构在临床试验过程中将采集的数据录入计算机化系统时,应当附带相应的元数据,包括稽查轨迹。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【相关元数据】 申办者、主要研究者和临床试验机构应当采用适当的方法应用、评估、访问、管理和审核元数据。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【数据更正】 申办者和临床试验机构应当制定更正数据错误的相关流程,申办者和主要研究者及时更正可能影响试验结果可靠性的数据错误并确保更正过程的可追溯性。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【数据传输、交换和迁移】 申办者、主要研究者和临床试验机构应当建立经验证的流程,确保在计算机化系统之间传输的电子数据(包括相关元数据)的可靠性、可追溯性和安全性,以及避免数据丢失或被篡改。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【分析前数据集的最终确认】 申办者应当界定符合质量标准的中期和最终分析数据,采取及时且可靠的流程,进行数据的采集、核对、验证、审核和错误更正以及在可能的情况下修正对试验参与者的安全性和/或试验结果的可靠性造成重要影响的遗漏。统计分析前,应当按照预先制定的程序对数据集进行最终确认,数据提取和分析集的确定应当遵循统计分析计划,并予以记录。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五十二条【数据治理中的盲态保持】 盲法试验中,临床试验各阶段都应当保持盲态完整性,并采取适当的盲态管理措施防止意外破盲导致试验偏倚。
|
||||||
|
|
||||||
|
在临床试验开始实施前,所有相关方应当确定访问非盲信息的角色、职责和流程,并做好记录;实施过程中,应当记录破盲或揭盲的情况,评估其对试验结果的影响,并采取相应必要的措施。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五十三条【计算机化系统】 临床试验各方应当确保用于临床试验的计算机化系统满足对试验数据可靠性、可追溯性和安全性的要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)【计算机化系统的使用规程和培训】 应当制定计算机化系统设置、安装和使用的标准操作规程,明确使用计算机化系统时临床试验各方的职责,确保在临床试验数据采集、处理和管理过程中正确使用计算机化系统。所有使用计算机化系统的人员应当经过培训。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)【计算机化系统的安全管理】 计算机化系统的数据安全管理应当包含试验数据和记录的整个数据生命周期,确保对计算机化系统实施安全控制,并采取持续措施预防、检测及减少安全漏洞。
|
||||||
|
|
||||||
|
应当及时对计算机化系统产生的试验数据充分备份,并在系统故障时采取应急措施,防止数据发生丢失或无法访问。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)【计算机化系统的验证】 临床试验各方使用的计算机化系统,应当通过可靠的系统验证,符合预期用途和预先设置的技术性能,以保证试验数据的可靠性,并保证在整个试验过程中系统始终处于验证有效的状态。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)【技术支持】 临床试验各方应当建立工作流程,记录、评估和管理计算机化系统中出现的问题,并定期对收集的问题进行审查,识别重复或系统性问题,根据问题的严重程度进行相应地处理。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(五)【用户管理】 计算机化系统应当具备完善的用户管理、权限管理和稽查轨迹,确保只有经授权的用户方可访问和使用系统,实现访问和操作可追溯。如使用电子签名应当符合我国关于电子签名的有关要求。用户的权限应当符合其职责职能、盲法设置和用户所属组织。授权用户及其权限应当明确记录、维护和保存。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第六章 附 则
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
第五十四条 本规范下列用语的含义是:
|
||||||
|
|
||||||
|
(一)主要研究者,是药物临床试验机构试验实施团队的负责人,对临床试验实施过程中试验现场试验参与者权益、安全及临床试验数据可靠性负责。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)其他潜在的严重安全性风险信息,指明显影响药品获益风险评估、可能改变药品用法或影响总体药品研发进程的信息。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)药物临床试验质量管理,对药物临床试验的质量实施管理,包括建立质量管理体系和开展具体的质量管理活动。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(四)药物临床试验质量管理体系,对临床试验全过程质量进行管理的机制,以试验参与者保护与数据可靠性为核心,明确界定并落实各方的职责与分工,能够基于风险,及时识别、预防及处理异常事件,持续推动质量改进,保障药物临床试验过程规范。
|
||||||
|
|
||||||
|
本规范自XXXX年X月X日起施行。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
附件2
|
||||||
|
|
||||||
|
《药物临床试验质量管理规范(修订稿
|
||||||
|
|
||||||
|
征求意见稿)》起草说明
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
一、起草背景
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
现行《药物临床试验质量管理规范》实施(以下简称2020版GCP)已有近5年时间。2020版GCP实施以来对我国药物临床试验规范化发展发挥了重要作用,有效指导了我国临床试验监管和实施实践。近年来,随着药物研发技术和理念的持续更新,国际监管规范也在发生重大调整,国际人用药品注册技术协调会(ICH)《E6(R3):药物临床试验质量管理规范技术指导原则》(即ICH GCP)自2019年启动修订,已于2025年1月定稿为ICH E6(R3)。为进一步推动我国药物临床试验高质量发展,对2020版GCP进行修订。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
二、修订的主要考虑
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
《药物临床试验质量管理规范》(以下简称GCP)是国家药监局发布的规范性文件,指导监管部门、申请人、药物临床试验机构及其他接受委托的机构开展药物临床试验工作,是监管执法依据。修订的主要考虑如下:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)做好与相关法律法规、其他规范性文件以及技术指导原则衔接。一是法律法规层面,与《药品管理法》《疫苗管理法》《药品管理法实施条例》《药品注册管理办法》等上位法做好对接。二是规范性文件层面,与《药物临床试验机构管理规定》《药物临床试验机构监督检查办法(试行)》、国家卫生健康委等四部门发布的《涉及人的生命科学和医学研究伦理审查办法》等规范性文件做好衔接,已有专门规范性文件规定的内容不再重复,GCP中留好接口,伦理审查部分主要保留药物临床试验相关特定要求。三是指导原则层面,重点做好与ICH E6(R3)衔接,保持基本原则和要求一致;E6(R3)中遵循当地监管要求的,进一步明确监管要求,我国相关规范性文件要求高于E6(R3)的以我国监管要求为准;E6(R3)中属于建议性做法和理念性描述,而非监管刚性要求的不纳入我国GCP,而是采取全文实施E6(R3)方式,为临床试验实施灵活性预留空间;同步启动核查、检查要点修订,我国其他技术指导原则要与GCP和E6(R3)做好衔接,根据实际需要开展制、修订工作,逐步完善我国GCP制度的技术指导原则体系,加强实施指导。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)充分吸收2020版GCP的经验。2020版GCP具备较好的工作基础,其篇章结构、文字表述,以及适应我国实践的特有规定,在GCP修订时予以合理保留和借鉴。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)注重解决我国临床试验实践中的问题。以问题为导向,针对监管部门和临床试验各参与方反映的问题,注意研究提出解决措施。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
三、主要内容
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
《药物临床试验质量管理规范(修订稿征求意见稿)》(以下简称GCP修订稿征求意见稿)分为总则、伦理审查委员会、主要研究者和药物临床试验机构、申办者、数据治理、附则6个章节,54个条款。
|
||||||
|
|
||||||
|
与2020版GCP相比本次修订保留了总则、伦理审查委员会、主要研究者和药物临床试验机构、申办者、附则5个章节,增加了数据治理1个章节。考虑到全文实施E6(R3)中文版,只保留我国特有的术语及其定义,删除试验方案、研究者手册、必备文件管理3个章节。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
四、需要说明的问题
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(一)GCP修订稿征求意见稿与ICH E6(R3)相比重点增删的内容
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1.试验参与者表述:参考E6(R3)中文版,将“受试者”改为“试验参与者”,突出试验参与者在临床试验中的主动性,体现对个体权益和主体性的尊重。
|
||||||
|
|
||||||
|
2.主要删减内容包括:主要研究者与药物临床试验机构章节删除知情同意书包含的细节,申办者章节删除监查和稽查的具体要求,删除试验方案、研究者手册、必备文件管理3个章节,GCP修订稿征求意见稿中保留原则性要求,具体操作参照E6(R3)中文版。
|
||||||
|
|
||||||
|
3.总则中保留并强调了利益冲突回避原则,增加支持新技术使用原则。伦理审查委员会章节保留2020版GCP审查中应当特别关注的情形以及伦理审查委员会应当受理并妥善处理试验参与者的相关诉求。增加伦理审查委员会文件保存以及提供记录要求。
|
||||||
|
|
||||||
|
4.将E6(R3)中“适用当地监管要求”进行了转化:伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定;优化安全性信息的报告流程,明确相关责任方;明确临床试验必备记录保存年限;明确生物等效性试验生物样品留样要求等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(二)GCP修订稿征求意见稿与2020版GCP相比增删的内容
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
法律法规依据增加了《药品注册管理办法》。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2.增加质量源于设计、风险相称及切合目的的原则性表述,并将三个理念贯穿各章节。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3.增加新技术、新方法在临床试验应用的原则。鼓励使用新技术、新方法支持药物临床试验开展,应符合当前伦理、科学和相关法律法规要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
4.进一步优化安全性事件报告流程及相应审查要求:主要研究者应当立即向申办者和伦理审查委员会报告严重不良事件(SAE)。可疑且非预期严重不良反应(SUSAR)和药物研发期间安全性更新报告(DSUR)由申办者报告主要研究者和伦理审查委员会,SUSAR报告的方式与需采取措施的紧迫性和临床试验用药品安全性特征的变化相称。申办者应当将SUSAR、其他潜在的严重安全性风险信息快速报告监管部门;对药品监督管理部门提出的风险处理要求、其他潜在的严重安全性风险信息、需要立即关注或采取措施的紧急安全性问题应当快速通知伦理审查委员会和主要研究者。伦理审查委员会应当重点关注并及时审查安全性事件,除2020版GCP规定的内容外,增加主要研究者报告的SAE、严重持续不依从问题、其他潜在的严重安全性风险信息;伦理审查SUSAR和DSUR的方式应遵循风险相称原则。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5.完善伦理审查委员会章节内容。明确伦理审查委员会开展伦理审查工作应当符合卫生健康主管部门有关规定,并履行药品监督管理部门提出的监管要求。删除伦理审查委员会组成与运行具体内容,总结凝练为“组成运行要求”一款,修订审查意见类型和内容要求,与《涉及人的生命科学和医学研究伦理审查办法》保持一致。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6.进一步明确临床试验质量最终责任人为申办者,临床试验现场最终责任人为主要研究者。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7.主要研究者与药物临床试验机构章节。根据E6(R3)术语对研究者的定义,研究者是负责实施临床试验的人员(单个人),如果试验由团队实施其负责人为主要研究者(单个人),考虑到我国药物临床试验均由团队实施的实际情况,将“研究者”修改为“主要研究者”。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
8.申办者章节。增加申办者监督职责、盲态保持和风险管理有关内容,以及疫苗试验用药品管理和使用要求。在本章节中将实操细节删除,按照E6(R3)中文版执行,必要时,在核查要点中体现。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
9.强调质量管理。申办者采用适合的体系,对临床试验全过程进行质量管理。药物临床试验机构应当建立药物临床试验质量管理体系,并确保其有效运行。实施质量管理应当基于风险,明确了风险识别与评估、风险控制、风险沟通、风险审查和风险报告的要求及主要考虑因素。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
10.增加数据治理章节。明确各方对数据治理的职责与要求。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
11.附则。除本条款纳入的术语及其定义外,本规范涉及的其他术语及其定义参考ICH E6(R3)中文版术语表。纳入适应我国临床试验实践的术语,包括主要研究者、其他潜在的严重安全性风险信息、药物临床试验质量管理以及药物临床试验质量管理体系。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(三)关于药物临床试验必备文件
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
为指导和规范药物临床试验必备文件的保存,国家药监局2020年6月3日发布了《药物临床试验必备文件保存指导原则》。GCP修订过程中,将其与E6(R3)附件1中的《附录C:临床试验实施的必备记录》进行了对比,对比认为《药物临床试验必备文件保存指导原则》的有关要求均包含在附录C中,且附录C对必备文件进行了合理扩展。E6(R3)中文版原文实施可替代《药物临床试验必备文件保存指导原则》,GCP修订稿中将原“必备文件”表述修改为“必备记录”。
|
||||||
|
|
@ -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”。
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
Risk Management in Clinical Trials: Why Does It Matter?
|
||||||
|
Risk is a part of life, whether personal or professional and in the professional world of clinical trials, these risks are a strong focus of the study design. They have to be, because the entire foundation of medicine, let alone the safety of the patients and practitioners involved is at stake.
|
||||||
|
|
||||||
|
Risks relating to quality, timeliness, safety, or budgets can affect the overall outcome of trials in several ways and it’s important when designing a study to be aware of them and to have a planned procedure for categorizing and mitigating them where possible.
|
||||||
|
|
||||||
|
Managing risk involves identification, assessment, planning, monitoring, and reacting to threats to the safe and efficacious desired outcomes of clinical trials and is a practice that is entirely necessary to maintain a standard of care and safety to the medical and patient communities.
|
||||||
|
|
||||||
|
As clinical trials become more complex, new risks emerge, and the essential nature of risk management is emphasized and put to the test. Every stakeholder in trials benefits from good risk management, from those interested in an ROI to those looking to further the science, and of course, those in need of urgent new treatments.
|
||||||
|
|
||||||
|
Risk assessment, therefore, means two, intimately-related things: it is both the program and its goal. A robust risk assessment program underlies effective management of risk, and it does this by comprising a series of activities or processes that follow the entire lifecycle of the product or treatment and striving to identify and remove or avoid any factor or process that threatens its quality.
|
||||||
|
|
||||||
|
Risk assessment achieves this by way of a risk management plan.
|
||||||
|
|
||||||
|
Forming a Clinical Trial Risk Management Plan
|
||||||
|
Risk management needs to begin at the moment of the trial’s conception. In this way, mitigation of risk will be woven into the protocol of the study itself, and the power of risk management is maximized throughout the trial. Before we get started on how to design this plan, the relevant documentation deserves a brief mention.
|
||||||
|
|
||||||
|
In terms of reference documents, there are two particularly useful sources of information to help with this:
|
||||||
|
|
||||||
|
ICH Q9 – This is a guideline on quality risk management that covers areas such as hazard identification and risk ranking and filtering.
|
||||||
|
ISO 14971 – This is specifically aimed at medical devices, but contained a structured approach for effective risk management that applies to most studies as a whole.
|
||||||
|
It’s worth mentioning here, the ICH E6 R2, which covers some of the best practices of a more formal risk management process, and is discussed at the end of this article. There are also many other documents to refer to for more specific or generalized information, such as ISO 13485, which focuses on integrating active risk management in quality processes.
|
||||||
|
|
||||||
|
The first step to forming a risk management plan is to be aware of and present with these documents. This will make it far easier to go through the following stages thoroughly.
|
||||||
|
|
||||||
|
1 – Identify and Define the Objectives of Each Category
|
||||||
|
Now, risks need to be considered and broken down into categories based on the parts of the study they fall under. There are two ways to approach this. If you begin with the risks and work backward, you end up forming the objectives off the back of those risks. The other way is to start with objectives and allocate risks to each of them.
|
||||||
|
|
||||||
|
For example, objectives throughout the study may fit into one of the following categories:
|
||||||
|
|
||||||
|
Timelines – This could be meeting set patient recruitment rates or regulatory approval timelines.
|
||||||
|
Safety – Safety objectives may cover limiting the number of adverse effects or the number of patients dropping out due to these adverse effects.
|
||||||
|
Quality – The target number of protocol deviations or cases of incomplete subject diaries would be under this category.
|
||||||
|
Compliance – E.g., minimizing missing or incomplete ICF files, inadequate monitoring of investigations.
|
||||||
|
Budget – Increases in clinical trial duration or sites’ budgets could be an objective here.
|
||||||
|
This forms a foundation for the identification of risks under each category, and how these risks might affect the success of each objective. This stage could represent a section of its own, as it involves a thorough assessment and identification procedure, in which internal and external processes, people, systems, and technologies are all examined for their impact on the risk of the study.
|
||||||
|
|
||||||
|
However, these details will be specific to each case, so a summary will suffice here. Clear identification of these risks takes time and should involve multiple different stakeholders depending on the context but the objective should be the same: find and categorize each risk that may jeopardize the ideal outcome of the objectives you have set.
|
||||||
|
|
||||||
|
2 – Conduct a Root Cause Analysis (RCA)
|
||||||
|
The next step is a deeper assessment of the events and risks that you’ve identified, and a discussion about the tolerance limits. RCA is a critical component of risk assessment as it is a process of making everything more efficient. Starting by finding the cause of each risk, you’ll then be able to work out how to address it and whether or how critically it really needs to be addressed.
|
||||||
|
|
||||||
|
For example, if you identify an objective to keep recruitment rates above a certain threshold, you may then find that the rarity of the disease is a potential root cause of the risk that patients may be hard to find in time, which would therefore threaten your objective.
|
||||||
|
|
||||||
|
While this is part of the separate RCA process, doing a thorough job in the identification of objectives can include a lot of root cause identification organically. This stage is essentially about completing the thorough risk statement with the cause included, so some risks will be easier than others in this regard.
|
||||||
|
|
||||||
|
This completes a chain between risk and objectives and helps to pad out and illuminate the specific areas that will be involved in risk mitigation and action planning sections of the later process. This is then part of the overarching goal of either minimizing or eliminating risk entirely and is described by the FDA Guidelines as the necessary step of identifying and understanding the nature, locations, and causes of risks that affect the course of the trial.
|
||||||
|
|
||||||
|
Data is one of the core focuses of a robust risk assessment plan, as it carries with it some of the highest risks to multiple categories of objectives. Monitoring efforts, therefore, need to be focused on the most likely sources of error in the conduct of collecting and storing said data, and thresholds need to be set based on tolerance limits, as a way of setting trigger points of action, should the risk reach unacceptable levels.
|
||||||
|
|
||||||
|
3 – Assess the Likelihood and Impact of the Risks
|
||||||
|
As an offshoot of the RCA, a deeper dive into the impact of each risk and the chances of it occurring can be performed. Broken into its likelihood and impact, each risk can then be analyzed further. Different contexts will define the scale used for this, but each can typically be ranked from 1 to 5 or 1 to 3 on a risk matrix.
|
||||||
|
|
||||||
|
The purpose of this stage is to set yourself up to define your responses. Before you can respond to the risks, you need to know what severity rating they have in order to set priorities of action into your plan. A simple 1 to 5 scale may use impact rankings of Insignificant, Minor, Significant, Major, and Severe. These can be set against the likelihood rankings of Rare, Unlikely, Moderately Likely, Likely, and Almost Certain.
|
||||||
|
|
||||||
|
Using this matrix, you’ll immediately see how your risks group together in different priority rankings. Risks ranking at the top of both indices will be the ones that need to most immediate and involved attention, while risks at the other end of the spectrum will be ones that can be bumped down the priority list and potentially accepted.
|
||||||
|
|
||||||
|
When you have your rankings, it’s time to decide on what your course of action will be in response to each one.
|
||||||
|
|
||||||
|
4 – Decide on Your Risk Responses
|
||||||
|
You can divide your responses into categories too, in order to make it easier to define the specifics of each. Consider four categories of response:
|
||||||
|
|
||||||
|
Avoid – These are the risks that must be dealt with immediately. For example, if there can be no approval due to a design element of the trial, this is the highest impact of risk as the trial itself can’t continue and the design must be altered.
|
||||||
|
Transfer – If the team involved finds the risk outside of their control, it can be transferred to a more qualified group for decision-making in regard to its impact and the necessary responses.
|
||||||
|
Mitigate – These are risks that can be removed or reduced to a more comfortable ranking, either by reducing the likelihood or the impact. Mitigation could also involve increasing the risk detection chances.
|
||||||
|
Accept – Some risks are simply worth taking. If an objective is threatened by a risk that will pay off in the long run, and this risk can’t be mitigated in any other way, it’s classed as an acceptable risk.
|
||||||
|
For some specific examples, let’s say that a site offers significant benefits to the overall study (for example, a unique participant pool) but it’s in a location that could delay regulatory approval and the staff may not be as qualified as you would like to hit your compliance targets.
|
||||||
|
|
||||||
|
The delays in acceptance might be considered worthwhile to reach your target population, so you consider that risk acceptable. On the other hand, compliance issues jeopardize the entire outcome of the study, so staff monitoring becomes an important focus of mitigation in terms of the risk to GCP. Assigning deeper monitoring to this part of the trial increases the risk detection rate and minimizes its impact ranking to acceptable levels.
|
||||||
|
|
||||||
|
Risk Dynamics: The Importance of an Ongoing Assessment of Risk
|
||||||
|
While the clinical trial risk management plan should be a key focus of the early-stage trial design, it’s important to understand the dynamic nature of risks and how they change and evolve as the trial is ongoing.
|
||||||
|
|
||||||
|
Assessment should be maintained at intervals throughout the trial conduct and to its closing phases too. Some risks have a cascade effect, so if they show up, they create other areas that need to be assessed. Others can have one root cause at one stage of the trial and another later on, meaning the responses might need to be adjusted.
|
||||||
|
|
||||||
|
It’s important to consider risk management in clinical trials an ongoing practice and to design your management plan with this in mind.
|
||||||
|
|
||||||
|
ICH E6 R: Risk Management in Clinical Trials – Two Best Practices to Mitigate Risk
|
||||||
|
As risk management is a diverse practice that needs to be tailored specifically to each case, there are many factors that cannot be generalized. However, there are practices worth following that relate to risk management as a whole and some areas of the process worth getting into in more detail as they pertain to clinical trials of all kinds and are of particular significance to the risk management process.
|
||||||
|
|
||||||
|
In 2016, ICH published a document that represents a new formal standard of risk-based approaches to clinical trials. In the Quality management section of this revision, the document covers the concept of risk-based thinking and divides this into two fundamental aspects:
|
||||||
|
|
||||||
|
Define what is critical to success – Focus on what matters. In the development stages of the trial protocol, and at the “Identify and Define” stage of the management plan, it’s important to clearly identify specific data and processes that are critical to human protection and the reliability of the results of the trial. This is a matter of how to think in a way that prioritizes the desired outcome of risk management, rather than the process. Your risk management plan is about the return on the significant investment into clinical trials from every stakeholder, whether it’s money, time, or therapeutic treatment. A study’s risk management helps bring these returns to life, resulting in a safe, effective, and powerful set of data that covers the needs of everyone involved.
|
||||||
|
Manage the critical elements of a clinical trial – This relates to the ongoing and dynamic nature of the risks involved in clinical trials. During the whole lifecycle of the study, it’s important to maintain an effort of identifying, evaluating, controlling, communicating, reviewing, and reporting on risks.
|
||||||
|
Having everyone involved on the same page is a key component to successful risk management. Breaking down the thinking around risk into these two categories opens up the way for all stakeholders to take part in the following suggested best practices:
|
||||||
|
|
||||||
|
Provide adequate support to clinical trial teams – Consider assigning a Risk Manager as a champion of the risk management process. They can be responsible for guiding thought processes and controlling appropriate documentation.
|
||||||
|
Start early – Since risk presents itself at every stage of the trial, including protocol design, risk management should precede it.
|
||||||
|
Balance critical thinking and available resources – It’s important to use the available resources to help identify risk, but beware of relying on them too rigidly. Risk managers should be able to navigate these documents while maintaining critical thinking in the RM team.
|
||||||
|
Promote cross-organizational risk identification – Include as many perspectives as possible, including even the sponsors and vendors, where appropriate.
|
||||||
|
Set appropriate thresholds – Setting between 3 and 5 predefined quality tolerance limits is key to keeping things from getting over-complicated and to illuminate systemic, protocol-level issues that can impact outcomes.
|
||||||
|
Integrate multiple data sources when rereviewing risk registers to drive the right downstream decisions – And perform risk assessment at regular intervals to assess if mitigations are effective and whether new risks are emerging.
|
||||||
|
Communicate regularly internally and externally – Ensure real-time access to the risk register is available to reduce latency and improve communications access to the most current information.
|
||||||
|
Apply lessons learned – Adapt in response to root cause analyses in the form of lessons-learned activities.
|
||||||
|
Conclusion
|
||||||
|
Risk assessment is a systematic process of identifying, analyzing, and responding to events or processes that jeopardize a trial’s objectives. Risks come in many shapes and forms, and from all directions, and it is in the effective management of these risks that trial design and execution is able to run with the best possible outcomes for all stakeholders.
|
||||||
|
|
||||||
|
Start early, and break the process into stages of identification, root cause analysis, likelihood and impact evaluation, and tolerance thresholds. From there, follow some best practices and maintain critical thinking while applying these principles throughout the study.
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
费用发票底稿(医疗、护理、营养、交通等),,,,,,,,预期费用,,,
|
||||||
|
支出项目,日期,金额,医保金额,自费金额,不予支持金额,,,项目,原由,金额,预估逻辑
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
,,,,,,,,,,,
|
||||||
|
总计,,,,,,,,,,,
|
||||||
|
|
|
@ -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】试算赔偿、补偿金额,与申请人(受试者)沟通方案设计与执行。,,,,,,,,,,,,
|
||||||
|
|
|
@ -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】,,,,,,,,,,,,,,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
方案与IB变更情况,,,,
|
||||||
|
,入组时,受试者入组时,差异点,评判
|
||||||
|
入选标准,,,,
|
||||||
|
排除标准,,,,
|
||||||
|
治疗组别,,,,
|
||||||
|
用药方案,,,,
|
||||||
|
安全性信息,,,,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 577 KiB |
|
After Width: | Height: | Size: 736 KiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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>© 2025 RMO一站式临床试验风险管理. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.layout {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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` +
|
||||||
|
'· 建议每人保额:80–120 万\n· 每次事故限额:400–600 万\n· 预估年保费区间:约 1.1 万–1.4 万元\n(实际以各保司精准报价为准)'
|
||||||
|
)
|
||||||
|
setGeneratingQuote(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGetPreciseQuote = async () => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
if (window.confirm('获取精准报价需先登录,是否前往登录?')) {
|
||||||
|
handleClose()
|
||||||
|
navigate('/login', { state: { from: { pathname: '/dashboard/project-quotes' } } })
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!aiQuote) return
|
||||||
|
setSendingPrecise(true)
|
||||||
|
// Mock: 系统整合资料发送至各保司
|
||||||
|
await new Promise((r) => setTimeout(r, 1000))
|
||||||
|
setPreciseSent(true)
|
||||||
|
setSendingPrecise(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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>,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
|
RMO(Risk 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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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: 保险主要保障SUSAR、SAE等首要风险,包括身故、残疾赔偿金等。具体保障范围请参考保险合同条款。</p>
|
||||||
|
</div>
|
||||||
|
<div className="faq-item">
|
||||||
|
<h3>Q: 理赔申请需要多长时间?</h3>
|
||||||
|
<p>A: 正常情况下,在收集到完整资料后,7-10个工作日内完成详细评估,5个工作日内提出赔偿方案建议。</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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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>CRO、CDMO、SMO支持服务</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</PageContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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, cxo(密码:123456)</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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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-XXXX(24小时)</p>
|
||||||
|
<p><strong>服务邮箱:</strong>service@rmo.com</p>
|
||||||
|
<p><strong>服务时间:</strong>全天候24小时服务</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Participant
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||