Change to Vue Structure

This commit is contained in:
william.wan 2026-02-11 22:46:36 +08:00
parent 7d1cc40d95
commit 80470e008b
96 changed files with 5868 additions and 3204 deletions

View File

@ -0,0 +1,222 @@
隐私政策信息
隐私政策
发布日期2025 年 2 月 5 日
生效日期2025 年 2 月 5 日
上海达诺新晨信息科技有限公司(以下简称 "达诺" 或 "我们")负责处理您在本网站上的个人信息。
请仔细阅读本隐私政策,其中描述了我们收集有关访问本网站的个人信息的方式("个人数据"),我们如何收集、使用、存储、对外提供、保护您的个人信息及您享有何种权利。
本隐私政策自 2025 年 2 月 5 日起生效,除非本隐私政策的内容有所更新,其内容将持续有效。
1. 用户
通过使用我们的网站,您同意并接受向达诺提供能直接或间接识别您身份的信息(以下简称 "个人信息" 或 "个人数据"),并将这类信息收集、处理和存储在中国境内。
若您位于欧盟或欧洲经济区并通过我们的网站提供了您的个人信息,您有权根据欧盟《一般数据保护条例》(以下简称 "GDPR")获取任何有关达诺处理您个人信息的准确信息以及行使您在 GDPR 项下的权利。
2. 我们收集哪些您的个人信息
达诺收集并处理如下您的个人信息:
(1) 通过 "联系我们 - 业务咨询 / RFP""联系我们 - 媒体与投资者咨询" 与 "联系我们 - 客户反馈" 版块,我们可能会通过在线表格收集诸如您的姓名、电子邮件、联系电话、公司、职位、国家等信息;
(2) 如果您对达诺在业务活动及相关事宜中有任何疑虑,您可以通过 "联系我们 - 合规疑虑" 版块联系我们,您可以通过填写在线表格的方式报告该疑虑事件,我们可能会收集诸如您的姓名、电话、邮箱等个人信息;
(3) 如果您通过邮箱、电话或其他书面形式联系我们,达诺会保留您的相关信息。
除上述信息外,达诺还通过跟踪系统收集用户在浏览网站时产生的信息,这类信息包括:您的设备信息,包含用户 IP 地址或其他标识符,设备位置,浏览器信息,操作系统信息以及您的上网记录信息,包含浏览的页面和文件、搜索、操作系统和系统配置信息以及与您的使用相关的日期 / 时间戳,该等信息可能符合作为个人信息的条件。该信息用于分析总体趋势,帮助我们提供和改进我们的产品和服务,并保证其安全性和持续正常运行。具体内容,详见第 4 部分。
3. 我们如何使用您的个人信息
达诺根据个人信息首次收集之目的为限度存储和处理个人信息,并且不会超过数据处理目的之必要。当您的个人信息已不再需要被处理时,达诺将确保以安全的方式处理或删除您的个人信息。达诺已使用符合业界标准的安全防护措施保护您提供的个人信息,使其免受意外、非法或未经授权的破坏、损失、变更、修改、存取、披露或使用。
达诺基于合法的理由处理您的个人信息,这些法定依据包括:
(1) 为了与您缔结及履行合同之必要;
(2) 您同意您的个人信息为一个或多个特定目的而处理;
(3) 处理是基于达诺追求合法利益之必要,且该利益不与您所追求的利益或基本权利自由相冲突。
达诺基于如下目的使用您的个人信息:
(1) 个性化您在我们网站的体验;
(2) 为您提供我们的服务(我们通过使用您的个人信息为了与您缔约合同;回答您的疑虑;介绍我们的产品或业务等);
(3) 回复您发送给我们的邮件;
(4) 分析您在我们网站的使用习惯,以便达诺能不断改进网站和您的用户体验;
若您对达诺的合规有任何疑虑,达诺将使用您留下的联系方式与您联系。
若达诺是基于您的同意处理个人信息,您有权随时撤回同意并要求达诺删除有关您的任何信息。具体内容,详见第 11 部分。
4.Cookies
当您访问我们网站时,我们通过 Cookies 以及网络信标等去记录我们的业绩以及网站的使用情况。Cookie 是发送到您的浏览器上并在您的电脑硬盘驱动器上存储的小量数据。只有当您使用您的电脑进入我们网站时Cookie 才能够被发送到您的电脑硬盘上。 Cookies 常用于记录访问者浏览我们网站上的各个项目时的习惯以及偏好。Cookies 所搜集的资料是不记名的集体统计数据不载有个人资料。Cookies 不能用于取得您的硬盘上的数据、您的电邮地址、及您的私人数据。当您重新上访我们网站时,可以省却您再次登记的步骤。大多数浏览器都预设为可以接受 Cookies。您可以选择将您的浏览器设定为不接受 Cookies, 或如果 Cookies 一被装上就通知您。不过,若设定为禁止 Cookies, 您或许便不能启动或使用我们网站的某些功能。
若您不禁止或除去 cookies, 每次您使用同一台电脑进入我们的网站时,我们的网络服务器会通知我们你上访了我们网站,继而我们会辨认出您及接达您的登记数据及付款数据,搜集有关使用量、巿场研究、行迹进程及参与推广活动的资料等。
您可以改动您用来进入我们网站的电脑的浏览器上的设定来决定是否接受 cookie。若您愿意您可以改变浏览器上的设定。若您将您的偏好放在浏览器上让您可以接受所有的 cookies, 收到 cookies 发出的通知,甚至可以拒绝一切 cookies。然而若在您的浏览器选用不要 cookies 或拒绝所有 cookies, 您有可能不能使用或启动我们网站的某些功能,或有可能需要重新登入您的资料。
5. 个人信息的分享或披露
为了保证您的个人信息安全,我们承诺,所有的共享和披露仅限您的必要个人信息,且受本隐私声明的约束;如果我们要改变个人信息的使用及处理目的,我们将再次征求您的授权同意。同时我们将会严格遵守相关法律法规对数据跨境传输的各项要求。
我们会将您的个人信息分享或披露给以下第三方:
(1) 获得您的明确同意后,我们会与其他方共享或披露您的个人信息。
(2) 我们可能会将您的部分个人信息共享给为我们提供网站运营服务的第三方供应商;
(3) 共享给达诺集团成员。您可通过 "关于我们" 的版块自行查询达诺关联方的名称和联系方式及其位置列表。
如果我们出售或放弃我们的业务或部分业务且您的个人数据与此出售的或放弃的此部分业务有关,或我们同其它业务合并,我们将与此业务的新的所有者或并购伙伴共享您的个人数据;
如被依法强制要求,达诺将会与执法机构和其它政府机构共享我们收集的数据。
在上述情形下,达诺将实施适当的技术性和组织性措施,以确保与风险相适应的安全等级,防止任何未经授权或非法的处理,第三方或关联公司的意外毁损、丢失或损害。
6. 个人信息的跨境传输
原则上我们在中华人民共和国境内收集和产生的个人信息将存储在中华人民共和国境内。但基于统一集团运营和管理的需要我们可能会将您的个人信息传输或转移到境外管辖区或者受到来自这些管辖区的访问。该等国家可能并未设立与信息原始收集国一样的数据保护法律。若您是处于欧洲经济区且达诺将您的信息传输至欧洲经济区EEA之外的国家时达诺会依据其应适用的法律法规采取相应措施保护该等信息。
我们向其传输数据的接收方所在国家或地区的个人信息保护法律可能与中国的法律不同。当我们将您的个人信息转移到中国境外时,我们将采取适当措施保护您的个人信息,例如与接收方签订包含标准数据保护条款的数据转移协议或通过数据出境安全评估,并相应履行个人信息跨境传输的义务。
对于传输至欧洲经济区外的信息达诺使用被称之为标准合同条款SCC的方式确保恰当的足够的保护。
当您在使用达诺的网站和 / 或服务时或当您向达诺提供产品和 / 或服务时,我们收集的您的个人数据会被传输到我们在中国的集团公司。
7. 儿童的信息
达诺网站通常不收集 18 周岁以下未成年人的个人信息,且网站也不针对 18 周岁以下的未成年人。
如果有未成年人使用达诺的产品或网站的服务时,我们会遵循最小必要的原则和目的限制原则,在家长或监护人的明确同意下,直接从家长或监护人处收集未满 14 周岁未成年人的个人信息。
若您是不满 14 周岁的儿童的监护人,当您在帮助儿童完成网站产品或服务的注册、使用前,应当仔细阅读本隐私声明、产品或服务具体的隐私保护声明(如有)以便儿童能使用我们提供的产品或服务。
8. 个人信息的保存期限
达诺会按照您所在地区相关法律法规及行业惯例的规定保存您的个人信息。达诺只会在符合上述目的的情况下保存您的个人信息。
我们将始终按照法律要求的期限以及根据与法律诉讼或涉及到达诺的调查相关的需求来保留您的个人信息。在您的个人信息存储期限到期后,除根据法律法规规定必须继续存储更长时间的个人信息外,我们将对您的个人信息进行删除或匿名化处理,使其处于不可被访问的状态,并留存相关删除或匿名化处理记录。
9. 第三方网站
达诺的网站可能会提供第三方网站的链接,以便提供给用户更方便快捷的体验。达诺不对第三方网站做任何背书,不为该类第三方网站的内容、提供的服务、安全性以及可用性负责。同时,达诺的网站会使用第三方社交媒体,诸如微信,社交媒体可能会收集您的个人信息和 / 或使用 Cookies 功能。这些第三方网站不受本隐私政策的约束,因此,达诺建议您在访问第三方网站时详细阅读该类网站的隐私政策。
10. 安全
达诺已使用符合业界标准的安全防护措施保护您的个人信息,防止数据遭到未经授权的访问、公开披露、使用、修改、损坏或丢失。我们会采取一切合理可行的措施,保护您的个人信息。
但达诺无法保证您在网上披露的任何信息的安全性。若发生任何数据泄露事件,达诺按照法律法规的要求,及时向您告知:安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施等。我们将及时将事件相关情况以邮件、信函、电话、推送通知等方式告知您,难以逐一告知个人信息主体时,我们会采取合理、有效的方式发布公告。
请记住,虽然达诺会采取一切合理的措施保护您的个人信息的安全,但因互联网环境的特点,达诺不能保证所有您的个人信息的绝对安全,在适用法律的合理范围内,我们不承担任何未经授权访问、使用或披露您的个人信息的所有责任。
11. 您的权利和请求
(1) 知情权
达诺通过本隐私政策向您提供任何关于处理您的个人数据的信息。此外,您还可以通过本隐私政策第 13 条中的联系方式向达诺获取任何有关您个人数据处理的信息。
(2) 数据访问权
您有权从达诺处以免费的形式获取一份有关您个人信息处理的副本。
(3) 更正权
您有权要求达诺及时更正您不准确的个人信息。考虑到处理的目的,您应当有权将不完整的个人数据补充完整,包括通过提供补充声明的方式。
(4) 删除权(被遗忘权)
您有权基于如下理由要求达诺删除您的个人信息:
l 处理目的已实现、无法实现或者为实现处理目的不再必要;
l 我们不再为您提供产品或服务,或者保存期限已届满;
l 您撤回了同意;
l 我们违反法律、行政法规或者违反约定处理您的个人信息;
l 法律、行政法规规定的其他情形;
若法律、行政法规规定的保存期限未届满,或者删除您的个人信息从技术上难以实现的,我们将停止除存储和采取必要的安全措施之外的处理。
(5) 限制处理权
您有权基于如下理由要求限制达诺处理您的个人数据,我们会在解除限制之前通知您:
l 您对您个人数据的准确性提出质疑,有权利要求达诺在核实申请和更正数据所需的时间内对您个人数据的处理进行限制;
l 您认为达诺的处理是非法的,但您反对删除个人数据,有权利要求限制使用个人数据;
l 基于处理目的,达诺已不再需要您的个人数据,但您提出、行使或抗辩法律诉求而需要该个人数据。
(6) 可携带权
您有权获得您已向达诺提供的个人数据,以结构化的、普遍使用的机器可读的形式,并有权不受达诺限制地将该等数据提供给其他控制者。达诺不对该类接受者的数据处理承担任何责任。
(7) 反对权
您有权在特定情况下随时反对达诺对您个人信息的处理,除非达诺能够证明其合法利益高于您的权益、权利和自由,或者法定请求权的确立、行使和抗辩有强有力的法律依据。
(8) 撤回同意的权利
若达诺是基于您的同意处理个人信息,您有权随时撤回同意。同意的撤回不应影响在撤回前基于同意作出的数据处理的合法性。
(9) 申诉权
当您认为您的权利受到侵害时,您有权向人民法院依法提起诉讼。
您可以通过本隐私政策第 13 条中的联系方式行使您的上述权利。达诺在收到您的任何请求时,会在 15 个工作日内做出答复。
12. 本隐私政策如何更新
通过在本网站上发布新的隐私政策,我们可能会不时更改或更新此隐私政策。
未经您明确同意,我们不会削减您按照本隐私政策所应享有的权利。我们会在本页面上发布对本政策所做的任何变更。
13. 联系我们
如果您对本隐私政策或者达诺的数据保护有任何疑问、意见或建议,通过以下方式与我们联系:
邮箱RMO@vdano.com
公司:上海达诺新晨信息科技有限公司(简称:达诺)
上海达诺新晨信息科技有限公司

View File

@ -7,8 +7,8 @@
<title>RMO一站式临床试验风险管理</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

2706
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,28 +3,23 @@
"version": "1.0.0",
"description": "RMO一站式临床试验风险管理网站",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
"lint": "eslint . --ext ts,tsx,vue --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0"
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.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",
"@vitejs/plugin-vue": "^5.0.0",
"@vue/tsconfig": "^0.5.0",
"typescript": "^5.2.2",
"vite": "^5.0.8"
"vite": "^5.0.8",
"vue-tsc": "^1.8.0"
}
}

BIN
pic/Home_Page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

BIN
public/pic/Home_Page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

View File

@ -1,125 +0,0 @@
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

8
src/App.vue Normal file
View File

@ -0,0 +1,8 @@
<template>
<RouterView />
<QuoteRequestModal />
</template>
<script setup lang="ts">
import QuoteRequestModal from '@/components/QuoteRequestModal.vue'
</script>

View File

@ -0,0 +1,164 @@
.cookie-consent-overlay {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 99999 !important;
padding: 0;
background: transparent;
animation: slideUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.cookie-consent {
max-width: 100%;
margin: 0;
background: var(--white, #ffffff) !important;
border-top: 1px solid var(--border-color, #e0e0e0);
padding: 20px 24px;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
width: 100%;
box-sizing: border-box;
}
.cookie-consent-content {
flex: 1;
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 24px;
}
.cookie-consent-title {
font-size: 16px;
font-weight: 600;
margin: 0;
color: var(--text-color);
white-space: nowrap;
flex-shrink: 0;
}
.cookie-consent-text {
font-size: 14px;
line-height: 1.6;
color: var(--text-light);
margin: 0;
flex: 1;
}
.cookie-link {
color: var(--brand-primary, #0ea5e9);
text-decoration: underline;
transition: color 0.2s ease;
}
.cookie-link:hover {
color: var(--brand-primary-dark, #0284c7);
}
.cookie-consent-actions {
display: flex;
gap: 12px;
flex-shrink: 0;
margin-left: auto;
}
.cookie-btn {
padding: 10px 24px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
white-space: nowrap;
min-width: 80px;
}
.cookie-btn-reject {
background: var(--white);
color: var(--text-color);
border: 1px solid var(--border-color, #e0e0e0);
}
.cookie-btn-reject:hover {
background: var(--bg-color, #f5f5f5);
border-color: var(--text-light, #999);
}
.cookie-btn-accept {
background: var(--brand-primary, #0ea5e9);
color: var(--white);
border: 1px solid var(--brand-primary);
}
.cookie-btn-accept:hover {
background: var(--brand-primary-dark, #0284c7);
border-color: var(--brand-primary-dark);
}
@media (max-width: 768px) {
.cookie-consent {
flex-direction: column;
padding: 16px 20px;
gap: 16px;
align-items: flex-start;
}
.cookie-consent-content {
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
.cookie-consent-title {
font-size: 16px;
}
.cookie-consent-text {
font-size: 13px;
line-height: 1.5;
}
.cookie-consent-actions {
width: 100%;
display: flex;
gap: 12px;
margin-left: 0;
}
.cookie-btn {
flex: 1;
padding: 10px 20px;
}
}
@media (max-width: 480px) {
.cookie-consent {
padding: 16px;
}
.cookie-consent-actions {
flex-direction: column;
}
.cookie-btn {
width: 100%;
}
}

View File

@ -0,0 +1,78 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import './CookieConsent.css'
function CookieConsent() {
const [showConsent, setShowConsent] = useState(false)
useEffect(() => {
// 检查用户是否已经做出过选择
try {
const consent = localStorage.getItem('cookieConsent')
console.log('[CookieConsent] Checking localStorage, consent =', consent)
if (!consent) {
console.log('[CookieConsent] No consent found, will show after delay')
// 延迟显示,让页面先加载完成
const timer = setTimeout(() => {
console.log('[CookieConsent] Setting showConsent to true')
setShowConsent(true)
}, 1000) // 减少延迟时间到1秒
return () => {
console.log('[CookieConsent] Cleaning up timer')
clearTimeout(timer)
}
} else {
console.log('[CookieConsent] Consent already given, not showing. To test, clear localStorage: localStorage.removeItem("cookieConsent")')
}
} catch (error) {
console.error('[CookieConsent] Error accessing localStorage:', error)
// 如果localStorage不可用仍然显示
const timer = setTimeout(() => {
setShowConsent(true)
}, 1000)
return () => clearTimeout(timer)
}
}, [])
const handleAccept = () => {
console.log('CookieConsent: User accepted')
localStorage.setItem('cookieConsent', 'accepted')
setShowConsent(false)
}
const handleReject = () => {
console.log('CookieConsent: User rejected')
localStorage.setItem('cookieConsent', 'rejected')
setShowConsent(false)
}
console.log('CookieConsent: Rendering, showConsent =', showConsent)
if (!showConsent) {
return null
}
return (
<div className="cookie-consent-overlay">
<div className="cookie-consent">
<div className="cookie-consent-content">
<h3 className="cookie-consent-title">Cookies</h3>
<p className="cookie-consent-text">
使 Cookies 使 Cookies 使 Cookies 使 Cookies <Link to="/privacy-policy" className="cookie-link">Cookies </Link>
</p>
</div>
<div className="cookie-consent-actions">
<button onClick={handleReject} className="cookie-btn cookie-btn-reject">
</button>
<button onClick={handleAccept} className="cookie-btn cookie-btn-accept">
</button>
</div>
</div>
</div>
)
}
export default CookieConsent

View File

@ -0,0 +1,54 @@
<template>
<div v-if="showConsent" class="cookie-consent-overlay">
<div class="cookie-consent">
<div class="cookie-consent-content">
<h3 class="cookie-consent-title">Cookies</h3>
<p class="cookie-consent-text">
本网站使用 Cookies 以使您获得最佳的体验为了继续浏览本网站您需同意我们对 Cookies 的使用想要了解更多有关于 Cookies 的信息或不希望当您使用网站时出现 Cookies请阅读我们的
<RouterLink to="/privacy-policy" class="cookie-link">Cookies 声明</RouterLink>
</p>
</div>
<div class="cookie-consent-actions">
<button type="button" @click="handleReject" class="cookie-btn cookie-btn-reject">拒绝</button>
<button type="button" @click="handleAccept" class="cookie-btn cookie-btn-accept">接受</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const showConsent = ref(false)
onMounted(() => {
try {
const consent = localStorage.getItem('cookieConsent')
if (!consent) {
const timer = setTimeout(() => {
showConsent.value = true
}, 1000)
return () => clearTimeout(timer)
}
} catch {
const timer = setTimeout(() => {
showConsent.value = true
}, 1000)
return () => clearTimeout(timer)
}
})
function handleAccept() {
localStorage.setItem('cookieConsent', 'accepted')
showConsent.value = false
}
function handleReject() {
localStorage.setItem('cookieConsent', 'rejected')
showConsent.value = false
}
</script>
<style scoped>
@import './CookieConsent.css';
</style>

View File

@ -0,0 +1,155 @@
<template>
<div class="dashboard-layout">
<aside :class="['dashboard-sidebar', sidebarOpen ? 'open' : 'closed']">
<div class="sidebar-header">
<RouterLink to="/dashboard" class="sidebar-logo">
<h2>RMO</h2>
<span>工作台</span>
</RouterLink>
<button type="button" class="sidebar-toggle" @click="sidebarOpen = !sidebarOpen" aria-label="切换侧边栏">
{{ sidebarOpen ? '◀' : '▶' }}
</button>
</div>
<nav class="sidebar-nav">
<RouterLink
to="/dashboard"
:class="['nav-item', isDashboardHome ? 'active' : '']"
>
<span class="nav-icon">📊</span>
<span v-show="sidebarOpen" class="nav-text">工作台</span>
</RouterLink>
<template v-if="isPolicyholder">
<RouterLink to="/dashboard/project-quotes" :class="['nav-item', { active: isActiveParent('/dashboard/project-quotes') }]">
<span class="nav-icon">💰</span>
<span v-show="sidebarOpen" class="nav-text">项目报价</span>
</RouterLink>
<RouterLink to="/dashboard/projects" :class="['nav-item', { active: isActiveParent('/dashboard/projects') }]">
<span class="nav-icon">📋</span>
<span v-show="sidebarOpen" class="nav-text">项目列表</span>
</RouterLink>
</template>
<template v-if="isInsurer">
<RouterLink to="/dashboard/inquiries" :class="['nav-item', { active: isActiveParent('/dashboard/inquiries') }]">
<span class="nav-icon">💼</span>
<span v-show="sidebarOpen" class="nav-text">询价列表</span>
</RouterLink>
</template>
<template v-if="isPolicyholder || isInsurer">
<RouterLink to="/dashboard/claims" :class="['nav-item', { active: isActiveParent('/dashboard/claims') }]">
<span class="nav-icon">📝</span>
<span v-show="sidebarOpen" class="nav-text">理赔进度</span>
</RouterLink>
</template>
<div class="nav-group">
<div :class="['nav-item', { active: isActiveParent('/dashboard/tools') }]">
<span class="nav-icon">🛠</span>
<span v-show="sidebarOpen" class="nav-text">智能工具</span>
</div>
<div v-show="isActiveParent('/dashboard/tools')" class="nav-submenu">
<RouterLink to="/dashboard/tools/premium-calculator" :class="['nav-subitem', { active: route.path === '/dashboard/tools/premium-calculator' }]">保费测算工具</RouterLink>
<RouterLink v-if="isPolicyholder" to="/dashboard/tools/icf-editor" :class="['nav-subitem', { active: route.path === '/dashboard/tools/icf-editor' }]">ICF智能修改</RouterLink>
<RouterLink v-if="isPolicyholder" to="/dashboard/tools/risk-scoring" :class="['nav-subitem', { active: route.path === '/dashboard/tools/risk-scoring' }]">方案风险评分</RouterLink>
<RouterLink to="/dashboard/tools/protocol-risk" :class="['nav-subitem', { active: route.path === '/dashboard/tools/protocol-risk' }]">方案风险评估</RouterLink>
<RouterLink to="/dashboard/tools/drug-safety" :class="['nav-subitem', { active: route.path === '/dashboard/tools/drug-safety' }]">药安查</RouterLink>
</div>
</div>
</nav>
</aside>
<div class="dashboard-main">
<header class="dashboard-header">
<div class="header-left">
<RouterLink to="/" class="header-logo">
<h2>RMO</h2>
<span>生命科学风险管理</span>
</RouterLink>
<nav class="header-nav">
<RouterLink to="/" :class="{ active: route.path === '/' }">首页</RouterLink>
<div class="nav-dropdown" @mouseenter="concernMenuOpen = true" @mouseleave="concernMenuOpen = false">
<RouterLink to="/concern" :class="{ active: isActiveParentMulti(concernPaths) }">风险职责</RouterLink>
<div v-show="concernMenuOpen" class="dropdown-menu">
<RouterLink to="/sponsor">申办者职责</RouterLink>
<RouterLink to="/holder">持有人职责</RouterLink>
<RouterLink to="/participant">受试者专区</RouterLink>
<RouterLink to="/institution">研究中心</RouterLink>
<RouterLink to="/service-provider">CXO职责</RouterLink>
</div>
</div>
<div class="nav-dropdown" @mouseenter="rmoMenuOpen = true" @mouseleave="rmoMenuOpen = false">
<RouterLink to="/rmo-mode" :class="{ active: isActiveParentMulti(rmoPaths) }">临床试验</RouterLink>
<div v-show="rmoMenuOpen" class="dropdown-menu">
<RouterLink to="/rmo-mode/insurance">保险方案</RouterLink>
<RouterLink to="/rmo-mode/guarantee">保证方案</RouterLink>
<RouterLink to="/rmo-mode/insurance-guarantee">保险保证</RouterLink>
</div>
</div>
<RouterLink to="/post-market" :class="{ active: route.path.startsWith('/post-market') }">上市应用</RouterLink>
<RouterLink to="/overseas" :class="{ active: route.path === '/overseas' }">海外风险</RouterLink>
<div class="nav-dropdown" @mouseenter="systemMenuOpen = true" @mouseleave="systemMenuOpen = false">
<RouterLink to="/system-management" :class="{ active: isActiveParentMulti(systemPaths) }">资源中心</RouterLink>
<div v-show="systemMenuOpen" class="dropdown-menu">
<RouterLink to="/system-management/laws">法律法规</RouterLink>
<RouterLink to="/system-management/practice-guide">实践指南</RouterLink>
<RouterLink to="/system-management/training">培训材料</RouterLink>
<RouterLink to="/system-management/faq">常见问题</RouterLink>
</div>
</div>
</nav>
</div>
<div class="header-right">
<div class="user-info">
<span class="user-name">{{ auth.user?.name }}</span>
<span class="user-role">{{ auth.user?.role }}</span>
</div>
<button type="button" class="logout-btn" @click="handleLogout">退出登录</button>
</div>
</header>
<main class="dashboard-content">
<RouterView />
</main>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
const sidebarOpen = ref(true)
const rmoMenuOpen = ref(false)
const concernMenuOpen = ref(false)
const systemMenuOpen = ref(false)
const concernPaths = ['/concern', '/sponsor', '/holder', '/institution', '/service-provider', '/participant']
const rmoPaths = ['/rmo-mode', '/insurance', '/guarantee', '/insurance-guarantee']
const systemPaths = ['/system-management', '/faq']
const isPolicyholder = computed(() => auth.user?.role === '投保人')
const isInsurer = computed(() => auth.user?.role === '保险人')
const isDashboardHome = computed(() =>
route.path === '/dashboard' &&
!['/dashboard/projects', '/dashboard/project-quotes', '/dashboard/inquiries', '/dashboard/claims', '/dashboard/tools'].some(p => route.path.startsWith(p))
)
function isActiveParent(path: string) {
return route.path.startsWith(path)
}
function isActiveParentMulti(paths: string[]) {
return paths.some(p => route.path.startsWith(p))
}
function handleLogout() {
auth.logout()
router.push('/login')
}
</script>
<style scoped>
@import './DashboardLayout.css';
</style>

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

@ -0,0 +1,32 @@
<template>
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h3>关于我们</h3>
<p>临研安致力于为临床试验提供一站式的风险管理解决方案保障受试者安全</p>
</div>
<div class="footer-section">
<h3>联系方式</h3>
<p>电话400-XXX-XXXX</p>
<p>邮箱info@rmo.com</p>
</div>
<div class="footer-section">
<h3>合作伙伴</h3>
<p>华泰保险经纪</p>
<p>临研安</p>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 RMO一站式临床试验风险管理. All rights reserved.</p>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
</script>
<style scoped>
@import './Footer.css';
</style>

View File

@ -4,20 +4,21 @@
top: 0;
left: 0;
right: 0;
background: var(--white);
border-bottom: 1px solid var(--border-color, #e0e0e0);
background: #ffffff;
border-bottom: 1px solid #e5e7eb;
z-index: 1000;
height: 56px;
height: 64px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
height: 56px;
max-width: 1200px;
height: 64px;
max-width: 1400px;
margin: 0 auto;
padding: 0 24px;
padding: 0 32px;
}
.logo {
@ -29,15 +30,15 @@
}
.logo h1 {
font-size: 22px;
font-weight: 600;
color: var(--brand-primary);
font-size: 24px;
font-weight: 700;
color: #1f2937;
letter-spacing: -0.02em;
}
.logo span {
font-size: 13px;
color: var(--text-light, #666);
font-size: 12px;
color: #6b7280;
font-weight: 400;
}
@ -55,12 +56,12 @@
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 16px;
color: var(--text-color, #333);
font-size: 14px;
padding: 0 20px;
color: #1f2937;
font-size: 15px;
font-weight: 500;
text-decoration: none;
transition: color 0.15s ease, background 0.15s ease;
transition: color 0.2s ease, background 0.2s ease;
position: relative;
box-sizing: border-box;
}
@ -68,13 +69,13 @@
.nav > a:hover,
.nav .nav-dropdown:hover > a,
.nav .nav-dropdown:hover .nav-dropdown-trigger {
color: var(--brand-primary);
color: #0ea5e9;
}
.nav > a.active,
.nav .nav-dropdown > a.active,
.nav .nav-dropdown-trigger.active {
color: var(--brand-primary);
color: #0ea5e9;
font-weight: 600;
}
@ -85,14 +86,15 @@
content: '';
position: absolute;
bottom: 0;
left: 16px;
right: 16px;
height: 2px;
background: var(--brand-primary);
left: 20px;
right: 20px;
height: 3px;
background: #0ea5e9;
}
.nav .nav-dropdown {
height: 100%;
position: relative;
}
.nav .nav-dropdown > a,
@ -102,13 +104,15 @@
.dropdown-arrow {
font-size: 10px;
margin-left: 4px;
opacity: 0.8;
margin-left: 6px;
opacity: 0.7;
transition: transform 0.2s ease;
color: #6b7280;
}
.nav-dropdown:hover .dropdown-arrow {
transform: rotate(180deg);
opacity: 1;
}
/* 下拉面板白底、细边框、左对齐vdano 风格) */
@ -116,33 +120,46 @@
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
background: var(--white);
border: 1px solid var(--border-color, #e0e0e0);
min-width: 220px;
background: #ffffff;
border: 1px solid #e5e7eb;
border-top: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
padding: 8px 0;
z-index: 1001;
animation: fadeInDown 0.2s ease-out;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dropdown-menu a {
display: block;
padding: 10px 20px;
color: var(--text-color, #333);
padding: 10px 24px;
color: #374151;
font-size: 14px;
font-weight: 400;
text-decoration: none;
transition: background 0.15s ease, color 0.15s ease;
transition: background 0.2s ease, color 0.2s ease;
white-space: nowrap;
}
.dropdown-menu a:hover {
background: rgba(14, 165, 233, 0.06);
color: var(--brand-primary);
background: #f3f4f6;
color: #0ea5e9;
}
.dropdown-menu a.active {
background: rgba(14, 165, 233, 0.08);
color: var(--brand-primary);
background: #eff6ff;
color: #0ea5e9;
font-weight: 500;
}
@ -150,87 +167,142 @@
display: none;
}
/* 三级菜单:风险活动 - 分组标题与子项 */
.dropdown-divider {
height: 1px;
background: #e5e7eb;
margin: 8px 12px;
border: none;
}
/* 三级菜单多列布局vdano 风格) */
.dropdown-menu-level3-wrapper {
min-width: 220px;
padding: 8px 0;
display: flex;
min-width: 680px;
padding: 0;
border-top: none;
}
.dropdown-submenu {
padding: 6px 0;
border-bottom: 1px solid var(--border-color, #eee);
flex: 1;
padding: 20px 0;
border-right: 1px solid #e5e7eb;
min-width: 200px;
}
.dropdown-submenu:last-of-type {
border-bottom: none;
border-right: none;
}
/* 如果子菜单只有标题链接,调整样式 */
.dropdown-submenu:has(.dropdown-submenu-title-link:only-child) {
min-width: 180px;
}
.dropdown-submenu-title {
padding: 8px 20px 6px;
font-size: 12px;
color: var(--text-light, #888);
padding: 0 24px 10px;
font-size: 11px;
color: #9ca3af;
font-weight: 600;
letter-spacing: 0.02em;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 6px;
}
.dropdown-submenu .dropdown-submenu-title-link {
display: block;
padding: 10px 20px;
font-size: 14px;
font-weight: 400;
color: var(--text-color, #333);
padding: 10px 24px;
font-size: 15px;
font-weight: 600;
color: #1f2937;
margin-bottom: 6px;
transition: background 0.2s ease, color 0.2s ease;
position: relative;
}
.dropdown-submenu .dropdown-submenu-title-link:hover {
background: rgba(14, 165, 233, 0.06);
color: var(--brand-primary);
background: #f3f4f6;
color: #0ea5e9;
}
.dropdown-submenu .dropdown-submenu-title-link.active {
background: #eff6ff;
color: #0ea5e9;
font-weight: 600;
}
/* 如果标题链接是唯一子元素,增加底部间距 */
.dropdown-submenu:has(.dropdown-submenu-title-link:only-child) .dropdown-submenu-title-link {
margin-bottom: 0;
}
.dropdown-submenu a:not(.dropdown-submenu-title-link) {
padding: 8px 20px 8px 28px;
font-size: 13px;
color: var(--text-color, #333);
display: block;
padding: 8px 24px 8px 36px;
font-size: 14px;
color: #4b5563;
font-weight: 400;
transition: background 0.2s ease, color 0.2s ease;
position: relative;
}
.dropdown-submenu a:not(.dropdown-submenu-title-link)::before {
content: '•';
position: absolute;
left: 24px;
color: #9ca3af;
font-size: 12px;
}
.dropdown-submenu a:not(.dropdown-submenu-title-link):hover {
background: rgba(14, 165, 233, 0.06);
color: var(--brand-primary);
background: #f3f4f6;
color: #0ea5e9;
}
.dropdown-submenu a:not(.dropdown-submenu-title-link):hover::before {
color: #0ea5e9;
}
.dropdown-submenu a:not(.dropdown-submenu-title-link).active {
background: #eff6ff;
color: #0ea5e9;
font-weight: 500;
}
/* 风险活动触发项(无链接,仅展开) */
.nav-dropdown-trigger {
color: var(--text-color, #333);
font-size: 14px;
color: #1f2937;
font-size: 15px;
font-weight: 500;
padding: 0 16px;
padding: 0 20px;
cursor: default;
user-select: none;
}
.nav-dropdown-trigger.active::after {
left: 16px;
right: 16px;
left: 20px;
right: 20px;
}
/* 登录按钮vdano 风格多为线框或文字按钮 */
.login-btn {
margin-left: 16px;
padding: 8px 20px;
margin-left: 20px;
padding: 8px 24px;
font-size: 14px;
font-weight: 500;
color: var(--brand-primary);
color: #0ea5e9;
background: transparent;
border: 1px solid var(--brand-primary);
border-radius: 4px;
border: 1px solid #0ea5e9;
border-radius: 6px;
text-decoration: none;
transition: background 0.15s ease, color 0.15s ease;
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
display: inline-flex;
align-items: center;
}
.login-btn:hover {
background: var(--brand-primary);
color: var(--white);
background: #0ea5e9;
color: #ffffff;
border-color: #0ea5e9;
}
.login-btn.active,
@ -287,14 +359,14 @@
@media (max-width: 768px) {
.header {
height: auto;
min-height: 56px;
min-height: 64px;
}
.header-content {
flex-direction: column;
align-items: stretch;
height: auto;
padding: 12px 16px;
padding: 12px 20px;
}
.nav {
@ -319,10 +391,26 @@
.dropdown-menu {
position: static;
box-shadow: none;
border: 1px solid var(--border-color, #eee);
border-radius: 4px;
border: 1px solid #e5e7eb;
border-radius: 6px;
margin-top: 4px;
margin-left: 12px;
min-width: auto;
}
.dropdown-menu-level3-wrapper {
flex-direction: column;
min-width: auto;
}
.dropdown-submenu {
border-right: none;
border-bottom: 1px solid var(--border-color, #eee);
padding: 12px 0;
}
.dropdown-submenu:last-of-type {
border-bottom: none;
}
.login-btn {

View File

@ -7,10 +7,9 @@ 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 [aboutRmoOpen, setAboutRmoOpen] = useState(false)
const [solutionsOpen, setSolutionsOpen] = useState(false)
const [knowledgeOpen, setKnowledgeOpen] = useState(false)
const isActive = (path: string) => location.pathname === path
const isActiveParent = (paths: string[]) => paths.some(p => location.pathname.startsWith(p))
@ -33,106 +32,102 @@ function Header() {
</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>
{/* 一级:风险活动(二级+三级菜单:临床试验下含保险方案等,上市应用) */}
{/* 一级关于RMO二级+三级菜单) */}
<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)}
onMouseEnter={() => setAboutRmoOpen(true)}
onMouseLeave={() => setAboutRmoOpen(false)}
>
<Link
to="/system-management"
className={isActiveParent(['/system-management', '/faq', '/system-management/laws']) ? 'active' : ''}
to="/about/overview"
className={isActiveParent(['/about', '/concern', '/sponsor', '/holder', '/institution', '/service-provider', '/participant']) ? 'active' : ''}
>
RMO
<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>
{aboutRmoOpen && (
<div className="dropdown-menu dropdown-menu-level2 dropdown-menu-level3-wrapper">
{/* RMO概述 */}
<div className="dropdown-submenu">
<Link to="/about/overview" className="dropdown-submenu-title-link">RMO概述</Link>
</div>
{/* 风险职责 */}
<div className="dropdown-submenu">
<Link to="/concern" className="dropdown-submenu-title-link"></Link>
<Link to="/sponsor"></Link>
<Link to="/holder"></Link>
<Link to="/institution"></Link>
<Link to="/participant"></Link>
<Link to="/service-provider">CXO</Link>
</div>
</div>
)}
</div>
{/* 一级:解决方案(二级菜单) */}
<div
className="nav-dropdown nav-dropdown-has-level3"
onMouseEnter={() => setSolutionsOpen(true)}
onMouseLeave={() => setSolutionsOpen(false)}
>
<span
className={`nav-dropdown-trigger ${isActiveParent(['/solutions', '/risk-data', '/rmo-mode', '/post-market', '/product-insurance']) ? 'active' : ''}`}
>
<span className="dropdown-arrow"></span>
</span>
{solutionsOpen && (
<div className="dropdown-menu dropdown-menu-level2 dropdown-menu-level3-wrapper">
{/* 药物警戒(风险数据) */}
<div className="dropdown-submenu">
<Link to="/solutions/pharmacovigilance" className="dropdown-submenu-title-link"></Link>
<Link to="/risk-data/pv-service">PV服务</Link>
<Link to="/risk-data/ai-tools">AI工具</Link>
</div>
{/* 临床保险(风险活动) */}
<div className="dropdown-submenu">
<Link to="/solutions/clinical-insurance" className="dropdown-submenu-title-link"></Link>
<Link to="/rmo-mode/insurance"></Link>
<Link to="/rmo-mode/guarantee"></Link>
</div>
{/* 产品责任 */}
<div className="dropdown-submenu">
<Link to="/solutions/product-insurance" className="dropdown-submenu-title-link"></Link>
<Link to="/post-market/insurance"></Link>
<Link to="/post-market/guarantee"></Link>
</div>
</div>
)}
</div>
{/* 一级:知识资源(二级菜单) */}
<div
className="nav-dropdown"
onMouseEnter={() => setKnowledgeOpen(true)}
onMouseLeave={() => setKnowledgeOpen(false)}
>
<Link
to="/knowledge/regulations"
className={isActiveParent(['/knowledge', '/system-management', '/faq']) ? 'active' : ''}
>
<span className="dropdown-arrow"></span>
</Link>
{knowledgeOpen && (
<div className="dropdown-menu dropdown-menu-level2">
<Link to="/knowledge/regulations"></Link>
<Link to="/knowledge/insurance"></Link>
<Link to="/knowledge/pv-insurance">PV与保险</Link>
<Link to="/faq"></Link>
</div>
)}
</div>
{/* 一级:联系我们 */}
<Link to="/contact" className={isActive('/contact') ? 'active' : ''}>
</Link>
{/* 登录/用户信息 */}
{isAuthenticated ? (
<div className="user-menu">

136
src/components/Header.vue Normal file
View File

@ -0,0 +1,136 @@
<template>
<header class="header">
<div class="container">
<div class="header-content">
<RouterLink to="/" class="logo">
<h1>RMO</h1>
<span>生命科学风险管理</span>
</RouterLink>
<nav class="nav nav-level1">
<RouterLink to="/" :class="{ active: route.path === '/' }">首页</RouterLink>
<div
class="nav-dropdown nav-dropdown-has-level3"
@mouseenter="aboutRmoOpen = true"
@mouseleave="aboutRmoOpen = false"
>
<RouterLink
to="/about/overview"
:class="{ active: isActiveParent(aboutPaths) }"
>
关于RMO
<span class="dropdown-arrow"></span>
</RouterLink>
<div v-show="aboutRmoOpen" class="dropdown-menu dropdown-menu-level2 dropdown-menu-level3-wrapper">
<div class="dropdown-submenu">
<RouterLink to="/about/overview" class="dropdown-submenu-title-link">RMO概述</RouterLink>
</div>
<div class="dropdown-submenu">
<RouterLink to="/concern" class="dropdown-submenu-title-link">风险职责</RouterLink>
<RouterLink to="/sponsor">申办者</RouterLink>
<RouterLink to="/holder">持有人</RouterLink>
<RouterLink to="/institution">研究中心</RouterLink>
<RouterLink to="/participant">参与者</RouterLink>
<RouterLink to="/service-provider">CXO</RouterLink>
</div>
</div>
</div>
<div
class="nav-dropdown nav-dropdown-has-level3"
@mouseenter="solutionsOpen = true"
@mouseleave="solutionsOpen = false"
>
<span :class="['nav-dropdown-trigger', { active: isActiveParent(solutionsPaths) }]">
解决方案
<span class="dropdown-arrow"></span>
</span>
<div v-show="solutionsOpen" class="dropdown-menu dropdown-menu-level2 dropdown-menu-level3-wrapper">
<div class="dropdown-submenu">
<RouterLink to="/solutions/pharmacovigilance" class="dropdown-submenu-title-link">药物警戒</RouterLink>
<RouterLink to="/risk-data/pv-service">PV服务</RouterLink>
<RouterLink to="/risk-data/ai-tools">AI工具</RouterLink>
</div>
<div class="dropdown-submenu">
<RouterLink to="/solutions/clinical-insurance" class="dropdown-submenu-title-link">临床保险</RouterLink>
<RouterLink to="/rmo-mode/insurance">保险方案</RouterLink>
<RouterLink to="/rmo-mode/guarantee">保证设计</RouterLink>
</div>
<div class="dropdown-submenu">
<RouterLink to="/solutions/product-insurance" class="dropdown-submenu-title-link">产品保险</RouterLink>
<RouterLink to="/post-market/insurance">保险方案</RouterLink>
<RouterLink to="/post-market/guarantee">保证设计</RouterLink>
</div>
</div>
</div>
<div
class="nav-dropdown"
@mouseenter="knowledgeOpen = true"
@mouseleave="knowledgeOpen = false"
>
<RouterLink
to="/knowledge/regulations"
:class="{ active: isActiveParent(knowledgePaths) }"
>
知识资源
<span class="dropdown-arrow"></span>
</RouterLink>
<div v-show="knowledgeOpen" class="dropdown-menu dropdown-menu-level2">
<RouterLink to="/knowledge/regulations">法规指南</RouterLink>
<RouterLink to="/knowledge/insurance">保险知识</RouterLink>
<RouterLink to="/knowledge/pv-insurance">PV与保险</RouterLink>
<RouterLink to="/faq">常见问题</RouterLink>
</div>
</div>
<RouterLink to="/contact" :class="{ active: route.path === '/contact' }">联系我们</RouterLink>
<template v-if="auth.isAuthenticated">
<div class="user-menu">
<RouterLink to="/dashboard" class="user-info-link">
<span class="user-name">{{ auth.user?.name }}</span>
<span class="user-role">{{ auth.user?.role }}</span>
</RouterLink>
<button type="button" class="logout-btn-header" @click="handleLogout">退出</button>
</div>
</template>
<template v-else>
<RouterLink to="/login" class="login-btn">登录</RouterLink>
</template>
</nav>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
const aboutRmoOpen = ref(false)
const solutionsOpen = ref(false)
const knowledgeOpen = ref(false)
const aboutPaths = ['/about', '/concern', '/sponsor', '/holder', '/institution', '/service-provider', '/participant']
const solutionsPaths = ['/solutions', '/risk-data', '/rmo-mode', '/post-market', '/product-insurance']
const knowledgePaths = ['/knowledge', '/system-management', '/faq']
function isActiveParent(paths: string[]) {
return paths.some(p => route.path.startsWith(p))
}
function handleLogout() {
auth.logout()
router.push('/')
}
</script>
<style scoped>
@import './Header.css';
</style>

View File

@ -6,6 +6,6 @@
.main-content {
flex: 1;
padding-top: 56px;
padding-top: 64px;
}

View File

@ -1,6 +1,7 @@
import { ReactNode } from 'react'
import Header from './Header'
import Footer from './Footer'
import CookieConsent from './CookieConsent'
import './Layout.css'
interface LayoutProps {
@ -15,6 +16,7 @@ function Layout({ children }: LayoutProps) {
{children}
</main>
<Footer />
<CookieConsent />
</div>
)
}

20
src/components/Layout.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<div class="layout">
<Header />
<main class="main-content">
<RouterView />
</main>
<Footer />
<CookieConsent />
</div>
</template>
<script setup lang="ts">
import Header from './Header.vue'
import Footer from './Footer.vue'
import CookieConsent from './CookieConsent.vue'
</script>
<style scoped>
@import './Layout.css';
</style>

View File

@ -0,0 +1,11 @@
<template>
<div :class="['page-container', compact ? 'page-container--compact' : '']">
<slot />
</div>
</template>
<script setup lang="ts">
defineProps<{
compact?: boolean
}>()
</script>

View File

@ -0,0 +1,21 @@
<template>
<div :class="variant === 'module' ? 'module-home-header' : 'page-header'">
<div class="header-content">
<div class="header-left">
<h2 class="page-title">{{ title }}</h2>
<p v-if="description" class="page-description">{{ description }}</p>
</div>
<div v-if="$slots.actions" class="header-actions">
<slot name="actions" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
title: string
description?: string
variant?: 'default' | 'module'
}>()
</script>

View File

@ -0,0 +1,35 @@
<template>
<div v-if="auth.loading" class="loading-screen">
<div>加载中...</div>
</div>
<RouterView v-else />
</template>
<script setup lang="ts">
import { watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const auth = useAuthStore()
const router = useRouter()
const route = useRoute()
watch(
() => [auth.loading, auth.isAuthenticated],
([loading, isAuth]) => {
if (!loading && !isAuth && route.path.startsWith('/dashboard')) {
router.replace({ path: '/login', query: { from: route.fullPath } })
}
},
{ immediate: true }
)
</script>
<style scoped>
.loading-screen {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
</style>

View File

@ -0,0 +1,173 @@
<template>
<div v-if="quoteModal.isOpen" class="quote-modal-overlay" @click="handleClose">
<div class="quote-modal" @click.stop>
<div class="quote-modal-header">
<h2 class="quote-modal-title">获取报价</h2>
<button type="button" class="quote-modal-close" @click="handleClose" aria-label="关闭">×</button>
</div>
<div class="quote-modal-body">
<p class="quote-modal-desc">请填写或上传报价所需资料生成报价后可向各保司获取精准报价</p>
<section class="quote-section">
<h3 class="quote-section-title">1. 报价需提交的资料</h3>
<div class="quote-fill-mode">
<label class="quote-radio">
<input type="radio" name="fillMode" :checked="fillMode === 'manual'" @change="fillMode = 'manual'" />
<span>手动填写</span>
</label>
<label class="quote-radio">
<input type="radio" name="fillMode" :checked="fillMode === 'upload'" @change="fillMode = 'upload'" />
<span>上传项目方案AI 识别填充</span>
</label>
</div>
<div v-if="fillMode === 'upload'" class="form-group">
<label>上传项目方案</label>
<input type="file" accept=".pdf,.doc,.docx" @change="handleUploadChange" />
<p v-if="uploading" class="form-hint">AI 识别中</p>
</div>
<div class="quote-form-grid">
<div class="form-group">
<label>项目方案编号</label>
<input v-model="formData.projectCode" type="text" placeholder="如CT-2025-001" />
</div>
<div class="form-group">
<label>项目标题</label>
<input v-model="formData.projectTitle" type="text" placeholder="试验方案标题" />
</div>
<div class="form-group">
<label>申办者</label>
<input v-model="formData.sponsor" type="text" placeholder="申办者名称" />
</div>
<div class="form-group">
<label>项目分期</label>
<input v-model="formData.projectPhase" type="text" placeholder="如I期、II期、III期" />
</div>
</div>
</section>
<section class="quote-section">
<h3 class="quote-section-title">2. 生成报价</h3>
<button
type="button"
class="btn btn-primary"
:disabled="!canGenerateQuote || generatingQuote"
@click="handleGenerateQuote"
>
{{ generatingQuote ? 'AI 生成中…' : '生成报价' }}
</button>
<div v-if="aiQuote" class="quote-ai-result">
<pre>{{ aiQuote }}</pre>
</div>
</section>
<section class="quote-section">
<h3 class="quote-section-title">3. 获取精准报价</h3>
<p class="quote-section-desc">系统将把报价资料整合后以邮件发送至各保司保司回复后将经临研安审核并回显到报价页面</p>
<button
type="button"
class="btn btn-primary"
:disabled="!aiQuote || sendingPrecise"
@click="handleGetPreciseQuote"
>
{{ sendingPrecise ? '发送中…' : preciseSent ? '已发送至各保司' : '获取精准报价' }}
</button>
<div v-if="preciseSent" class="quote-precise-tip">
<p>已向各保司发送询价邮件保司将回复至 rmo@vdano.com审核通过后将展示在报价页面</p>
<button
v-if="auth.isAuthenticated"
type="button"
class="btn btn-secondary btn-sm"
@click="goToQuotes"
>
前往报价页面查看
</button>
</div>
</section>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useQuoteModalStore } from '@/stores/quoteModal'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const quoteModal = useQuoteModalStore()
const auth = useAuthStore()
const fillMode = ref<'manual' | 'upload'>('manual')
const formData = ref({
projectCode: '',
projectTitle: '',
sponsor: '',
projectPhase: '',
})
const uploading = ref(false)
const aiQuote = ref<string | null>(null)
const generatingQuote = ref(false)
const preciseSent = ref(false)
const sendingPrecise = ref(false)
const canGenerateQuote = computed(() =>
formData.value.projectCode.trim() !== '' &&
formData.value.projectTitle.trim() !== '' &&
formData.value.sponsor.trim() !== '' &&
formData.value.projectPhase.trim() !== ''
)
function handleClose() {
quoteModal.closeQuoteModal()
formData.value = { projectCode: '', projectTitle: '', sponsor: '', projectPhase: '' }
aiQuote.value = null
preciseSent.value = false
}
async function handleUploadChange(e: Event) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
if (!file) return
uploading.value = true
await new Promise(r => setTimeout(r, 1200))
formData.value = {
projectCode: 'CT-2025-' + Math.floor(1000 + Math.random() * 9000),
projectTitle: file.name.replace(/\.[^.]+$/, '') || '临床试验方案',
sponsor: '示例申办者',
projectPhase: 'I期',
}
uploading.value = false
}
async function handleGenerateQuote() {
if (!canGenerateQuote.value) return
generatingQuote.value = true
await new Promise(r => setTimeout(r, 1500))
aiQuote.value =
`基于当前项目信息(${formData.value.projectTitle}${formData.value.projectPhase})的预估报价:\n` +
'· 建议每人保额80120 万\n· 每次事故限额400600 万\n· 预估年保费区间:约 1.1 万1.4 万元\n实际以各保司精准报价为准'
generatingQuote.value = false
}
async function handleGetPreciseQuote() {
if (!auth.isAuthenticated) {
if (window.confirm('获取精准报价需先登录,是否前往登录?')) {
handleClose()
router.push({ path: '/login', query: { from: '/dashboard/project-quotes' } })
}
return
}
if (!aiQuote.value) return
sendingPrecise.value = true
await new Promise(r => setTimeout(r, 1000))
preciseSent.value = true
sendingPrecise.value = false
}
function goToQuotes() {
handleClose()
router.push('/dashboard/project-quotes')
}
</script>
<style scoped>
@import './QuoteRequestModal.css';
</style>

7
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

14
src/main.ts Normal file
View File

@ -0,0 +1,14 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import { useAuthStore } from './stores/auth'
import './index.css'
import './styles/common.css'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
useAuthStore().restoreAuth()
app.use(router)
app.mount('#app')

View File

@ -1,15 +0,0 @@
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>,
)

102
src/pages/AboutOverview.css Normal file
View File

@ -0,0 +1,102 @@
.about-overview {
min-height: 100%;
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
margin-top: 32px;
}
.content-card {
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 32px;
}
.content-card h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-color);
}
.content-card p {
font-size: 16px;
color: var(--text-light);
line-height: 1.6;
margin: 0;
}
.content-card ul {
list-style: none;
padding: 0;
margin: 0;
}
.content-card li {
padding: 8px 0;
padding-left: 24px;
position: relative;
font-size: 16px;
color: var(--text-light);
line-height: 1.6;
}
.content-card li::before {
content: "✓";
position: absolute;
left: 0;
color: var(--brand-primary);
font-weight: bold;
}
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-top: 32px;
}
.service-item {
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 32px 24px;
text-align: center;
transition: all 0.3s ease;
}
.service-item:hover {
border-color: var(--brand-primary);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.1);
transform: translateY(-2px);
}
.service-icon {
font-size: 48px;
margin-bottom: 16px;
}
.service-item h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
color: var(--text-color);
}
.service-item p {
font-size: 14px;
color: var(--text-light);
line-height: 1.6;
margin: 0;
}
@media (max-width: 768px) {
.content-grid,
.services-grid {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,76 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './AboutOverview.css'
function AboutOverview() {
return (
<PageContainer>
<div className="about-overview">
<PageHeader
title="RMO概述"
description="生命科学风险管理平台"
/>
<div className="page-body">
<section className="section">
<div className="container">
<h2 className="section-title">RMO</h2>
<div className="content-grid">
<div className="content-card">
<h3>使</h3>
<p>
RMO致力于为生命科学行业提供全方位的风险管理解决方案
</p>
</div>
<div className="content-card">
<h3></h3>
<p>
</p>
</div>
<div className="content-card">
<h3></h3>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
</div>
</section>
<section className="section">
<div className="container">
<h2 className="section-title"></h2>
<div className="services-grid">
<div className="service-item">
<div className="service-icon">📊</div>
<h3></h3>
<p></p>
</div>
<div className="service-item">
<div className="service-icon">🛡</div>
<h3></h3>
<p></p>
</div>
<div className="service-item">
<div className="service-icon">💼</div>
<h3></h3>
<p></p>
</div>
<div className="service-item">
<div className="service-icon">📚</div>
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default AboutOverview

85
src/pages/Contact.css Normal file
View File

@ -0,0 +1,85 @@
.contact-page {
min-height: 100%;
}
.contact-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-top: 32px;
}
.contact-card {
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 32px 24px;
text-align: center;
}
.contact-card h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-color);
}
.contact-card p {
font-size: 16px;
color: var(--text-light);
line-height: 1.8;
margin: 8px 0;
}
.newsletter-form {
max-width: 600px;
margin: 32px auto 0;
}
.form-group {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.form-input {
flex: 1;
padding: 12px 16px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 16px;
transition: border-color 0.3s ease;
}
.form-input:focus {
outline: none;
border-color: var(--brand-primary);
}
.form-note {
font-size: 12px;
color: var(--text-light);
line-height: 1.6;
margin: 0;
text-align: center;
}
.privacy-link {
color: var(--brand-primary, #0ea5e9);
text-decoration: underline;
transition: color 0.2s ease;
}
.privacy-link:hover {
color: var(--brand-primary-dark, #0284c7);
}
@media (max-width: 768px) {
.contact-grid {
grid-template-columns: 1fr;
}
.form-group {
flex-direction: column;
}
}

72
src/pages/Contact.tsx Normal file
View File

@ -0,0 +1,72 @@
import { Link } from 'react-router-dom'
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Contact.css'
function Contact() {
return (
<PageContainer>
<div className="contact-page">
<PageHeader
title="联系我们"
description="获取RMO最新资讯第一时间了解我们的企业动态"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="contact-grid">
<div className="contact-card">
<h3></h3>
<p>clientservice@rmo.com</p>
<p>400-XXX-XXXX</p>
</div>
<div className="contact-card">
<h3>/RFP</h3>
<p>marketing@rmo.com</p>
<p>400-XXX-XXXX</p>
</div>
<div className="contact-card">
<h3></h3>
<p>PR@rmo.com</p>
<p>400-XXX-XXXX</p>
</div>
<div className="contact-card">
<h3></h3>
<p>compliance@rmo.com</p>
<p>400-XXX-XXXX</p>
</div>
</div>
</div>
</section>
<section className="section">
<div className="container">
<h2 className="section-title"></h2>
<p className="section-description">
RMO通讯简报
</p>
<form className="newsletter-form">
<div className="form-group">
<input
type="email"
placeholder="请输入您的邮箱地址"
className="form-input"
required
/>
<button type="submit" className="btn btn-primary">
</button>
</div>
<p className="form-note">
RMO承诺对您在本网页下提供的任何信息RMO<Link to="/privacy-policy" className="privacy-link"></Link>
</p>
</form>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default Contact

View File

@ -1,29 +1,125 @@
.hero {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: var(--white);
padding: 120px 0 80px;
text-align: center;
/* ========== 全屏滚动首页样式 ========== */
.home-fullscreen {
position: relative;
height: 100vh;
overflow-y: auto;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
}
.hero-compact {
padding: 60px 0 40px;
.home-section {
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
scroll-snap-align: start;
position: relative;
padding: 80px 24px 24px;
box-sizing: border-box;
}
/* ========== 滚动指示器 ========== */
.scroll-indicator {
position: fixed;
right: 24px;
top: 50%;
transform: translateY(-50%);
z-index: 999;
display: flex;
flex-direction: column;
gap: 12px;
}
.indicator-dot {
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid var(--brand-primary, #0ea5e9);
background: transparent;
cursor: pointer;
transition: all 0.3s ease;
padding: 0;
}
.indicator-dot:hover {
background: rgba(14, 165, 233, 0.3);
transform: scale(1.2);
}
.indicator-dot.active {
background: var(--brand-primary, #0ea5e9);
transform: scale(1.3);
}
/* ========== Hero Section ========== */
.hero-section {
background-image: url('/pic/Home_Page.png');
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
background-attachment: fixed;
color: var(--white);
text-align: center;
position: relative;
overflow: hidden;
min-height: 100vh;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(14, 165, 233, 0.25) 0%,
rgba(2, 132, 199, 0.25) 100%
);
z-index: 0;
pointer-events: none;
}
.hero-section::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.15);
z-index: 0;
pointer-events: none;
}
.hero-content {
max-width: 800px;
max-width: 900px;
margin: 0 auto;
z-index: 1;
position: relative;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.hero-title {
font-size: 48px;
font-size: 64px;
font-weight: 700;
margin-bottom: 20px;
margin-bottom: 24px;
line-height: 1.2;
letter-spacing: -0.02em;
color: var(--white);
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}
.hero-subtitle {
font-size: 20px;
margin-bottom: 40px;
opacity: 0.9;
font-size: 24px;
margin-bottom: 48px;
opacity: 0.95;
line-height: 1.6;
font-weight: 400;
color: var(--white);
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.hero-buttons {
@ -33,406 +129,286 @@
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 {
.hero-scroll-hint {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
width: 120px;
padding: 12px;
color: var(--white);
opacity: 0.8;
font-size: 14px;
animation: bounce 2s infinite;
}
.scroll-arrow {
font-size: 24px;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateX(-50%) translateY(0);
}
40% {
transform: translateX(-50%) translateY(-10px);
}
60% {
transform: translateX(-50%) translateY(-5px);
}
}
/* ========== Section通用样式 ========== */
.section-content {
max-width: 1200px;
margin: 0 auto;
width: 100%;
padding: 0 24px;
}
.section-title {
font-size: 48px;
font-weight: 600;
margin-bottom: 16px;
text-align: center;
color: var(--text-color);
letter-spacing: -0.02em;
}
.section-subtitle {
font-size: 20px;
color: var(--text-light);
text-align: center;
margin-bottom: 48px;
line-height: 1.6;
}
/* ========== 解决方案 Section ========== */
.solutions-section {
background: var(--white);
}
.solutions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 32px;
margin-top: 48px;
}
.solution-card {
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
padding: 40px 32px;
text-align: center;
text-decoration: none;
color: var(--text-color);
transition: transform 0.2s ease, box-shadow 0.2s ease;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
}
.home-quick-tool-icon:hover {
transform: scale(1.04);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
.solution-card:hover {
border-color: var(--brand-primary);
box-shadow: 0 8px 24px rgba(14, 165, 233, 0.15);
transform: translateY(-4px);
}
.home-quick-tool-icon img {
width: 88px;
height: auto;
display: block;
border-radius: 8px;
.solution-icon {
font-size: 64px;
margin-bottom: 24px;
}
.home-quick-tool-icon .quick-tool-emoji {
font-size: 48px;
.solution-card h3 {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-color);
}
.solution-card p {
font-size: 16px;
color: var(--text-light);
line-height: 1.6;
margin-bottom: 24px;
flex: 1;
}
.solution-link {
color: var(--brand-primary);
font-weight: 500;
font-size: 16px;
}
/* ========== 核心能力 Section ========== */
.capabilities-section {
background: linear-gradient(
135deg,
rgba(14, 165, 233, 0.05) 0%,
rgba(2, 132, 199, 0.05) 100%
);
}
.capabilities-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 48px;
margin-top: 48px;
}
.capability-item {
text-align: center;
}
.capability-number {
font-size: 56px;
font-weight: 700;
color: var(--brand-primary);
margin-bottom: 12px;
line-height: 1;
}
.home-quick-tool-icon .quick-tool-label {
font-size: 13px;
.capability-label {
font-size: 18px;
color: var(--text-light);
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 {
/* ========== 知识资源 Section ========== */
.knowledge-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 {
.knowledge-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 30px;
margin-top: 40px;
gap: 24px;
margin-top: 48px;
}
.nav-card {
background: var(--white);
border: 2px solid var(--border-color);
.knowledge-card {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 40px 30px;
text-align: center;
padding: 32px 24px;
text-decoration: none;
color: var(--text-color);
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);
.knowledge-card:hover {
border-color: var(--brand-primary);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.1);
transform: translateY(-2px);
}
.nav-icon {
font-size: 64px;
margin-bottom: 20px;
}
.nav-card h3 {
font-size: 22px;
margin-bottom: 15px;
.knowledge-card h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
color: var(--text-color);
}
.nav-card p {
.knowledge-card p {
font-size: 14px;
color: var(--text-light);
line-height: 1.6;
margin: 0;
}
/* ========== 联系我们 Section ========== */
.contact-section {
background: linear-gradient(
135deg,
rgba(14, 165, 233, 0.95) 0%,
rgba(2, 132, 199, 0.95) 100%
);
color: var(--white);
}
.contact-section .section-title,
.contact-section .section-subtitle {
color: var(--white);
}
.contact-actions {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
margin-top: 32px;
}
/* ========== 响应式设计 ========== */
@media (max-width: 768px) {
.home-section {
padding: 100px 16px 24px;
}
.hero-section {
background-attachment: scroll; /* 移动端禁用fixed提升性能 */
}
.hero-title {
font-size: 32px;
font-size: 36px;
}
.hero-subtitle {
font-size: 18px;
}
.section-title {
font-size: 32px;
}
.section-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 {
.solutions-grid,
.knowledge-grid {
grid-template-columns: 1fr;
gap: 24px;
}
.logic-content {
grid-template-columns: 1fr;
gap: 30px;
.capabilities-grid {
grid-template-columns: repeat(2, 1fr);
gap: 32px;
}
.logic-card {
padding: 30px 20px;
.scroll-indicator {
right: 12px;
}
.logic-header {
.hero-buttons,
.contact-actions {
flex-direction: column;
text-align: center;
gap: 10px;
align-items: stretch;
}
.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;
.hero-buttons .btn,
.contact-actions .btn {
width: 100%;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 28px;
}
.section-title {
font-size: 24px;
}
.capabilities-grid {
grid-template-columns: 1fr;
}
.capability-number {
font-size: 40px;
}
}

View File

@ -1,155 +1,201 @@
import { Link } from 'react-router-dom'
import PageContainer from '../components/PageContainer'
import { useEffect, useRef, useState } from 'react'
import { useQuoteModal } from '../contexts/QuoteModalContext'
import './Home.css'
function Home() {
const { openQuoteModal } = useQuoteModal()
const [currentSection, setCurrentSection] = useState(0)
const sectionsRef = useRef<(HTMLElement | null)[]>([])
useEffect(() => {
const handleWheel = (e: WheelEvent) => {
e.preventDefault()
const delta = e.deltaY > 0 ? 1 : -1
const nextSection = Math.max(0, Math.min(sectionsRef.current.length - 1, currentSection + delta))
if (nextSection !== currentSection) {
setCurrentSection(nextSection)
sectionsRef.current[nextSection]?.scrollIntoView({ behavior: 'smooth' })
}
}
window.addEventListener('wheel', handleWheel, { passive: false })
return () => window.removeEventListener('wheel', handleWheel)
}, [currentSection])
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const index = sectionsRef.current.indexOf(entry.target as HTMLElement)
if (index !== -1) {
setCurrentSection(index)
}
}
})
},
{ threshold: 0.5 }
)
sectionsRef.current.forEach((section) => {
if (section) observer.observe(section)
})
return () => observer.disconnect()
}, [])
return (
<PageContainer>
<div className="home">
{/* Hero Section */}
<section className="hero hero-compact">
<div className="container">
<div className="home-fullscreen">
{/* 导航指示器 */}
<div className="scroll-indicator">
{[0, 1, 2, 3, 4].map((index) => (
<button
key={index}
className={`indicator-dot ${currentSection === index ? 'active' : ''}`}
onClick={() => {
setCurrentSection(index)
sectionsRef.current[index]?.scrollIntoView({ behavior: 'smooth' })
}}
aria-label={`Section ${index + 1}`}
/>
))}
</div>
{/* Section 1: Hero - 主视觉 */}
<section
ref={(el) => (sectionsRef.current[0] = el)}
className="home-section hero-section"
style={{
backgroundImage: "url('/pic/Home_Page.png')",
backgroundPosition: 'center center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundAttachment: 'fixed'
}}
>
<div className="hero-content">
<h1 className="hero-title"></h1>
<h1 className="hero-title"></h1>
<p className="hero-subtitle">
</p>
<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}>
<Link to="/about/overview" className="btn btn-primary"></Link>
<button type="button" className="btn btn-secondary" onClick={openQuoteModal}>
</button>
</div>
</div>
<div className="hero-scroll-hint">
<span></span>
<div className="scroll-arrow"></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>
{/* Section 2: 解决方案 */}
<section
ref={(el) => (sectionsRef.current[1] = el)}
className="home-section solutions-section"
>
<div className="section-content">
<h2 className="section-title"></h2>
<p className="section-subtitle"></p>
<div className="solutions-grid">
<Link to="/solutions/pharmacovigilance" className="solution-card">
<div className="solution-icon">📊</div>
<h3></h3>
<p></p>
<p></p>
<span className="solution-link"> </span>
</Link>
<Link to="/solutions/clinical-insurance" className="solution-card">
<div className="solution-icon">🛡</div>
<h3></h3>
<p></p>
<span className="solution-link"> </span>
</Link>
<Link to="/solutions/product-insurance" className="solution-card">
<div className="solution-icon">💼</div>
<h3></h3>
<p></p>
<span className="solution-link"> </span>
</Link>
</div>
</div>
</section>
{/* Section 3: 核心能力 */}
<section
ref={(el) => (sectionsRef.current[2] = el)}
className="home-section capabilities-section"
>
<div className="section-content">
<h2 className="section-title"></h2>
<div className="capabilities-grid">
<div className="capability-item">
<div className="capability-number">10,000+</div>
<div className="capability-label"></div>
</div>
<div className="capability-item">
<div className="capability-number">3,600+</div>
<div className="capability-label"></div>
</div>
<div className="capability-item">
<div className="capability-number">180+</div>
<div className="capability-label"></div>
</div>
<div className="capability-item">
<div className="capability-number">130+</div>
<div className="capability-label"></div>
</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>
{/* Section 4: 知识资源 */}
<section
ref={(el) => (sectionsRef.current[3] = el)}
className="home-section knowledge-section"
>
<div className="section-content">
<h2 className="section-title"></h2>
<p className="section-subtitle"></p>
<div className="knowledge-grid">
<Link to="/knowledge/regulations" className="knowledge-card">
<h3></h3>
<p></p>
</Link>
<Link to="/holder" className="nav-card">
<div className="nav-icon">📋</div>
<h3></h3>
<p></p>
<Link to="/knowledge/insurance" className="knowledge-card">
<h3></h3>
<p></p>
</Link>
<Link to="/institution" className="nav-card">
<div className="nav-icon">🏥</div>
<h3></h3>
<p></p>
<Link to="/knowledge/pv-insurance" className="knowledge-card">
<h3>PV与保险</h3>
<p></p>
</Link>
<Link to="/participant" className="nav-card">
<div className="nav-icon">👤</div>
<h3></h3>
<p></p>
</Link>
<Link to="/service-provider" className="nav-card">
<div className="nav-icon">🤝</div>
<h3>CXO职责</h3>
<p>CROCDMOSMO支持服务</p>
<Link to="/faq" className="knowledge-card">
<h3></h3>
<p></p>
</Link>
</div>
</div>
</section>
{/* Section 5: 联系我们 */}
<section
ref={(el) => (sectionsRef.current[4] = el)}
className="home-section contact-section"
>
<div className="section-content">
<h2 className="section-title"></h2>
<p className="section-subtitle">RMO最新资讯</p>
<div className="contact-actions">
<Link to="/contact" className="btn btn-primary"></Link>
<Link to="/about/overview" className="btn btn-secondary">RMO</Link>
</div>
</div>
</section>
</div>
</PageContainer>
)
}

View File

@ -0,0 +1,44 @@
.knowledge-page {
min-height: 100%;
}
.knowledge-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
margin-top: 32px;
}
.knowledge-card {
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 32px 24px;
transition: all 0.3s ease;
}
.knowledge-card:hover {
border-color: var(--brand-primary);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.1);
transform: translateY(-2px);
}
.knowledge-card h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-color);
}
.knowledge-card p {
font-size: 16px;
color: var(--text-light);
line-height: 1.6;
margin: 0;
}
@media (max-width: 768px) {
.knowledge-content {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,38 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './KnowledgeBase.css'
function KnowledgeInsurance() {
return (
<PageContainer>
<div className="knowledge-page">
<PageHeader
title="保险知识"
description="保险方案设计与风险管理知识"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="knowledge-content">
<div className="knowledge-card">
<h3></h3>
<p></p>
</div>
<div className="knowledge-card">
<h3></h3>
<p></p>
</div>
<div className="knowledge-card">
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default KnowledgeInsurance

View File

@ -0,0 +1,38 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './KnowledgeBase.css'
function KnowledgePvInsurance() {
return (
<PageContainer>
<div className="knowledge-page">
<PageHeader
title="PV与保险"
description="药物警戒与保险的深度融合"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="knowledge-content">
<div className="knowledge-card">
<h3>PV数据在保险中的应用</h3>
<p></p>
</div>
<div className="knowledge-card">
<h3></h3>
<p>PV系统识别风险信号</p>
</div>
<div className="knowledge-card">
<h3></h3>
<p>PV与保险相结合</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default KnowledgePvInsurance

View File

@ -0,0 +1,38 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './KnowledgeBase.css'
function KnowledgeRegulations() {
return (
<PageContainer>
<div className="knowledge-page">
<PageHeader
title="法规指南"
description="最新的法律法规与实践指南"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="knowledge-content">
<div className="knowledge-card">
<h3></h3>
<p>GCP</p>
</div>
<div className="knowledge-card">
<h3></h3>
<p></p>
</div>
<div className="knowledge-card">
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default KnowledgeRegulations

131
src/pages/PrivacyPolicy.css Normal file
View File

@ -0,0 +1,131 @@
.privacy-policy-page {
min-height: 100%;
}
.privacy-content {
max-width: 900px;
margin: 0 auto;
padding: 40px;
line-height: 1.8;
}
.privacy-meta {
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border-color);
}
.privacy-meta p {
margin: 8px 0;
font-size: 14px;
color: var(--text-light);
}
.privacy-intro {
margin-bottom: 40px;
}
.privacy-intro p {
margin-bottom: 16px;
font-size: 16px;
color: var(--text-color);
}
.privacy-section {
margin-bottom: 40px;
}
.privacy-section h2 {
font-size: 24px;
font-weight: 600;
margin-bottom: 20px;
color: var(--text-color);
padding-bottom: 12px;
border-bottom: 2px solid var(--brand-primary);
}
.privacy-section p {
margin-bottom: 16px;
font-size: 16px;
color: var(--text-color);
line-height: 1.8;
}
.privacy-section ol {
margin: 16px 0;
padding-left: 24px;
}
.privacy-section ol li {
margin-bottom: 12px;
font-size: 16px;
color: var(--text-color);
line-height: 1.8;
}
.privacy-section ul {
margin: 12px 0;
padding-left: 24px;
list-style-type: disc;
}
.privacy-section ul li {
margin-bottom: 8px;
font-size: 15px;
color: var(--text-color);
line-height: 1.7;
}
.privacy-section strong {
font-weight: 600;
color: var(--text-color);
}
.contact-info {
background: var(--bg-color);
padding: 24px;
border-radius: 8px;
margin-top: 16px;
}
.contact-info p {
margin-bottom: 12px;
}
.contact-info a {
color: var(--brand-primary);
text-decoration: none;
}
.contact-info a:hover {
text-decoration: underline;
}
.privacy-footer {
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
text-align: center;
}
.privacy-footer p {
font-size: 16px;
font-weight: 500;
color: var(--text-color);
margin: 0;
}
@media (max-width: 768px) {
.privacy-content {
padding: 24px 20px;
}
.privacy-section h2 {
font-size: 20px;
}
.privacy-section p,
.privacy-section ol li {
font-size: 15px;
}
}

274
src/pages/PrivacyPolicy.tsx Normal file
View File

@ -0,0 +1,274 @@
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './PrivacyPolicy.css'
function PrivacyPolicy() {
return (
<PageContainer>
<div className="privacy-policy-page">
<PageHeader
title="隐私政策"
description="上海达诺新晨信息科技有限公司隐私政策"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="privacy-content card main-card">
<div className="privacy-meta">
<p><strong></strong>2025 2 5 </p>
<p><strong></strong>2025 2 5 </p>
</div>
<div className="privacy-intro">
<p>
"达诺" "我们"
</p>
<p>
访"个人数据"使
</p>
<p>
2025 2 5
</p>
</div>
<div className="privacy-section">
<h2>1. </h2>
<p>
使 "个人信息" "个人数据"
</p>
<p>
"GDPR"使 GDPR
</p>
</div>
<div className="privacy-section">
<h2>2. </h2>
<p></p>
<ol>
<li>
"联系我们 - 业务咨询 / RFP""联系我们 - 媒体与投资者咨询" "联系我们 - 客户反馈" 线
</li>
<li>
"联系我们 - 合规疑虑" 线
</li>
<li>
</li>
</ol>
<p>
IP 使 / 4
</p>
</div>
<div className="privacy-section">
<h2>3. 使</h2>
<p>
使使使
</p>
<p></p>
<ol>
<li></li>
<li></li>
<li></li>
</ol>
<p>使</p>
<ol>
<li></li>
<li>使</li>
<li></li>
<li>使便</li>
</ol>
<p>
使
</p>
<p>
11
</p>
</div>
<div className="privacy-section">
<h2>4. Cookies</h2>
<p>
访 Cookies 使Cookie 使Cookie Cookies 访Cookies Cookies 访 Cookies Cookies, Cookies Cookies, 便使
</p>
<p>
cookies, 使访使巿广
</p>
<p>
cookie cookies, cookies cookies cookies cookies, 使
</p>
</div>
<div className="privacy-section">
<h2>5. </h2>
<p>
使
</p>
<p></p>
<ol>
<li></li>
<li></li>
<li> "关于我们" </li>
</ol>
<p>
</p>
<p>
</p>
<p>
</p>
</div>
<div className="privacy-section">
<h2>6. </h2>
<p>
访EEA
</p>
<p>
</p>
<p>
使SCC
</p>
<p>
使 / /
</p>
</div>
<div className="privacy-section">
<h2>7. </h2>
<p>
18 18
</p>
<p>
使 14
</p>
<p>
14 使便使
</p>
</div>
<div className="privacy-section">
<h2>8. </h2>
<p>
</p>
<p>
使访
</p>
</div>
<div className="privacy-section">
<h2>9. </h2>
<p>
便便使 / 使 Cookies 访
</p>
</div>
<div className="privacy-section">
<h2>10. </h2>
<p>
使访使
</p>
<p>
</p>
<p>
访使
</p>
</div>
<div className="privacy-section">
<h2>11. </h2>
<ol>
<li>
<strong></strong>
<p> 13 </p>
</li>
<li>
<strong>访</strong>
<p></p>
</li>
<li>
<strong></strong>
<p></p>
</li>
<li>
<strong></strong>
<p></p>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<p></p>
</li>
<li>
<strong></strong>
<p></p>
<ul>
<li></li>
<li>使</li>
<li>使</li>
</ul>
</li>
<li>
<strong></strong>
<p>使</p>
</li>
<li>
<strong></strong>
<p>使</p>
</li>
<li>
<strong></strong>
<p></p>
</li>
<li>
<strong></strong>
<p></p>
</li>
</ol>
<p>
13 使 15
</p>
</div>
<div className="privacy-section">
<h2>12. </h2>
<p>
</p>
<p>
</p>
</div>
<div className="privacy-section">
<h2>13. </h2>
<p>
</p>
<div className="contact-info">
<p><strong></strong><a href="mailto:RMO@vdano.com">RMO@vdano.com</a></p>
<p><strong></strong></p>
</div>
</div>
<div className="privacy-footer">
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default PrivacyPolicy

59
src/pages/Solutions.css Normal file
View File

@ -0,0 +1,59 @@
.solutions-page {
min-height: 100%;
}
.solutions-nav {
display: flex;
gap: 16px;
margin-bottom: 32px;
flex-wrap: wrap;
}
.solution-nav-link {
padding: 12px 24px;
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 6px;
text-decoration: none;
color: var(--text-color);
font-weight: 500;
transition: all 0.3s ease;
}
.solution-nav-link:hover {
border-color: var(--brand-primary);
color: var(--brand-primary);
}
.solutions-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.solution-item {
background: var(--white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 32px 24px;
}
.solution-item h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-color);
}
.solution-item p {
font-size: 16px;
color: var(--text-light);
line-height: 1.6;
margin: 0;
}
@media (max-width: 768px) {
.solutions-content {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,43 @@
import { Link } from 'react-router-dom'
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Solutions.css'
function SolutionsClinicalInsurance() {
return (
<PageContainer>
<div className="solutions-page">
<PageHeader
title="临床保险"
description="为临床试验提供全面的保险保障服务"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="solutions-nav">
<Link to="/rmo-mode/insurance" className="solution-nav-link"></Link>
<Link to="/rmo-mode/guarantee" className="solution-nav-link"></Link>
</div>
<div className="solutions-content">
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default SolutionsClinicalInsurance

View File

@ -0,0 +1,47 @@
import { Link } from 'react-router-dom'
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Solutions.css'
function SolutionsPharmacovigilance() {
return (
<PageContainer>
<div className="solutions-page">
<PageHeader
title="药物警戒"
description="系统化的数据收集、监测和分析,识别潜在风险信号"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="solutions-nav">
<Link to="/risk-data/pv-service" className="solution-nav-link">PV服务</Link>
<Link to="/risk-data/ai-tools" className="solution-nav-link">AI工具</Link>
</div>
<div className="solutions-content">
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default SolutionsPharmacovigilance

View File

@ -0,0 +1,43 @@
import { Link } from 'react-router-dom'
import PageContainer from '../components/PageContainer'
import PageHeader from '../components/PageHeader'
import './Solutions.css'
function SolutionsProductInsurance() {
return (
<PageContainer>
<div className="solutions-page">
<PageHeader
title="产品保险"
description="上市后药物安全与风险管理保障"
/>
<div className="page-body">
<section className="section">
<div className="container">
<div className="solutions-nav">
<Link to="/post-market/insurance" className="solution-nav-link"></Link>
<Link to="/post-market/guarantee" className="solution-nav-link"></Link>
</div>
<div className="solutions-content">
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
<div className="solution-item">
<h3></h3>
<p></p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
)
}
export default SolutionsProductInsurance

View File

@ -38,9 +38,9 @@ function PremiumCalculator() {
<div className="calculator-card card main-card">
<form onSubmit={handleSubmit} className="calculator-form">
<div className="form-group">
<label htmlFor="projectType"></label>
<label htmlFor="premium-projectType"></label>
<select
id="projectType"
id="premium-projectType"
value={formData.projectType}
onChange={(e) => handleChange('projectType', e.target.value)}
required
@ -54,9 +54,9 @@ function PremiumCalculator() {
</div>
<div className="form-group">
<label htmlFor="riskLevel"></label>
<label htmlFor="premium-riskLevel"></label>
<select
id="riskLevel"
id="premium-riskLevel"
value={formData.riskLevel}
onChange={(e) => handleChange('riskLevel', e.target.value)}
required
@ -69,10 +69,10 @@ function PremiumCalculator() {
</div>
<div className="form-group">
<label htmlFor="coverageAmount"></label>
<label htmlFor="premium-coverageAmount"></label>
<input
type="number"
id="coverageAmount"
id="premium-coverageAmount"
value={formData.coverageAmount}
onChange={(e) => handleChange('coverageAmount', e.target.value)}
placeholder="请输入保障金额"
@ -81,10 +81,10 @@ function PremiumCalculator() {
</div>
<div className="form-group">
<label htmlFor="participantCount"></label>
<label htmlFor="premium-participantCount"></label>
<input
type="number"
id="participantCount"
id="premium-participantCount"
value={formData.participantCount}
onChange={(e) => handleChange('participantCount', e.target.value)}
placeholder="请输入受试者人数"
@ -93,10 +93,10 @@ function PremiumCalculator() {
</div>
<div className="form-group">
<label htmlFor="duration"></label>
<label htmlFor="premium-duration"></label>
<input
type="number"
id="duration"
id="premium-duration"
value={formData.duration}
onChange={(e) => handleChange('duration', e.target.value)}
placeholder="请输入试验周期"

View File

@ -53,9 +53,9 @@ function RiskScoring() {
<div className="scoring-card card main-card">
<form onSubmit={handleSubmit} className="scoring-form">
<div className="form-group">
<label htmlFor="projectType"></label>
<label htmlFor="riskScoring-projectType"></label>
<select
id="projectType"
id="riskScoring-projectType"
value={formData.projectType}
onChange={(e) => handleChange('projectType', e.target.value)}
required
@ -69,9 +69,9 @@ function RiskScoring() {
</div>
<div className="form-group">
<label htmlFor="drugType"></label>
<label htmlFor="riskScoring-drugType"></label>
<select
id="drugType"
id="riskScoring-drugType"
value={formData.drugType}
onChange={(e) => handleChange('drugType', e.target.value)}
required
@ -85,10 +85,10 @@ function RiskScoring() {
</div>
<div className="form-group">
<label htmlFor="participantCount"></label>
<label htmlFor="riskScoring-participantCount"></label>
<input
type="number"
id="participantCount"
id="riskScoring-participantCount"
value={formData.participantCount}
onChange={(e) => handleChange('participantCount', e.target.value)}
placeholder="请输入受试者人数"

137
src/router/index.ts Normal file
View File

@ -0,0 +1,137 @@
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/components/Layout.vue'
import DashboardLayout from '@/components/DashboardLayout.vue'
import ProtectedRoute from '@/components/ProtectedRoute.vue'
import { useAuthStore } from '@/stores/auth'
// 页面组件
import Home from '@/views/Home.vue'
import AboutOverview from '@/views/AboutOverview.vue'
import RiskDutiesOverview from '@/views/RiskDutiesOverview.vue'
import Sponsor from '@/views/Sponsor.vue'
import Holder from '@/views/Holder.vue'
import Institution from '@/views/Institution.vue'
import ServiceProvider from '@/views/ServiceProvider.vue'
import Participant from '@/views/Participant.vue'
import SolutionsPharmacovigilance from '@/views/SolutionsPharmacovigilance.vue'
import SolutionsClinicalInsurance from '@/views/SolutionsClinicalInsurance.vue'
import SolutionsProductInsurance from '@/views/SolutionsProductInsurance.vue'
import SmartAcquisition from '@/views/SmartAcquisition.vue'
import PVReport from '@/views/PVReport.vue'
import DrugSafetyDict from '@/views/DrugSafetyDict.vue'
import RmoMode from '@/views/RmoMode.vue'
import PostMarket from '@/views/PostMarket.vue'
import KnowledgeRegulations from '@/views/KnowledgeRegulations.vue'
import KnowledgeInsurance from '@/views/KnowledgeInsurance.vue'
import KnowledgePvInsurance from '@/views/KnowledgePvInsurance.vue'
import ResourceCenter from '@/views/ResourceCenter.vue'
import Overseas from '@/views/Overseas.vue'
import FAQ from '@/views/FAQ.vue'
import Contact from '@/views/Contact.vue'
import PrivacyPolicy from '@/views/PrivacyPolicy.vue'
import Login from '@/views/Login.vue'
// Dashboard
import Dashboard from '@/views/dashboard/Dashboard.vue'
import ProjectList from '@/views/dashboard/ProjectList.vue'
import ProjectDetail from '@/views/dashboard/ProjectDetail.vue'
import InquiryList from '@/views/dashboard/InquiryList.vue'
import InquiryDetail from '@/views/dashboard/InquiryDetail.vue'
import ClaimProgress from '@/views/dashboard/ClaimProgress.vue'
import ClaimDetail from '@/views/dashboard/ClaimDetail.vue'
import Tools from '@/views/dashboard/Tools.vue'
import PremiumCalculator from '@/views/dashboard/PremiumCalculator.vue'
import ProjectQuotes from '@/views/dashboard/ProjectQuotes.vue'
import ICFEditor from '@/views/dashboard/ICFEditor.vue'
import RiskScoring from '@/views/dashboard/RiskScoring.vue'
import ProtocolRiskAssessment from '@/views/dashboard/ProtocolRiskAssessment.vue'
import DrugSafetyQuery from '@/views/dashboard/DrugSafetyQuery.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Layout,
children: [
{ path: '', component: Home },
{ path: 'about/overview', component: AboutOverview },
{ path: 'concern', component: RiskDutiesOverview },
{ path: 'sponsor', component: Sponsor },
{ path: 'holder', component: Holder },
{ path: 'institution', component: Institution },
{ path: 'service-provider', component: ServiceProvider },
{ path: 'participant', component: Participant },
{ path: 'solutions/pharmacovigilance', component: SolutionsPharmacovigilance },
{ path: 'solutions/clinical-insurance', component: SolutionsClinicalInsurance },
{ path: 'solutions/product-insurance', component: SolutionsProductInsurance },
{ path: 'risk-data/smart-acquisition', component: SmartAcquisition },
{ path: 'risk-data/pv-report', component: PVReport },
{ path: 'risk-data/drug-safety-dict', component: DrugSafetyDict },
{ path: 'risk-data/pv-service', component: PVReport },
{ path: 'risk-data/ai-tools', component: SmartAcquisition },
{ path: 'rmo-mode', component: RmoMode },
{ path: 'rmo-mode/insurance', component: RmoMode },
{ path: 'rmo-mode/guarantee', component: RmoMode },
{ path: 'rmo-mode/insurance-guarantee', component: RmoMode },
{ path: 'post-market', component: PostMarket },
{ path: 'post-market/insurance', component: PostMarket },
{ path: 'post-market/guarantee', component: PostMarket },
{ path: 'knowledge/regulations', component: KnowledgeRegulations },
{ path: 'knowledge/insurance', component: KnowledgeInsurance },
{ path: 'knowledge/pv-insurance', component: KnowledgePvInsurance },
{ path: 'system-management', component: ResourceCenter },
{ path: 'system-management/laws', component: ResourceCenter },
{ path: 'system-management/practice-guide', component: ResourceCenter },
{ path: 'system-management/training', component: ResourceCenter },
{ path: 'system-management/faq', component: FAQ },
{ path: 'faq', component: FAQ },
{ path: 'contact', component: Contact },
{ path: 'privacy-policy', component: PrivacyPolicy },
{ path: 'overseas', component: Overseas },
{ path: 'login', component: Login },
]
},
{
path: '/dashboard',
component: ProtectedRoute,
children: [
{
path: '',
component: DashboardLayout,
children: [
{ path: '', component: Dashboard },
{ path: 'project-quotes', component: ProjectQuotes },
{ path: 'projects', component: ProjectList },
{ path: 'projects/:id', component: ProjectDetail },
{ path: 'inquiries', component: InquiryList },
{ path: 'inquiries/:id', component: InquiryDetail },
{ path: 'claims', component: ClaimProgress },
{ path: 'claims/:id', component: ClaimDetail },
{ path: 'tools', component: Tools },
{ path: 'tools/premium-calculator', component: PremiumCalculator },
{ path: 'tools/icf-editor', component: ICFEditor },
{ path: 'tools/risk-scoring', component: RiskScoring },
{ path: 'tools/protocol-risk', component: ProtocolRiskAssessment },
{ path: 'tools/drug-safety', component: DrugSafetyQuery },
]
}
]
}
]
})
router.beforeEach((to, from, next) => {
const auth = useAuthStore()
if (!auth.loading && auth.token === null && auth.user === null) {
auth.restoreAuth()
}
// 保护 /dashboard 路由
if (to.path.startsWith('/dashboard') && !auth.isAuthenticated && !auth.loading) {
next({ path: '/login', query: { from: to.fullPath } })
} else {
next()
}
})
export default router

78
src/stores/auth.ts Normal file
View File

@ -0,0 +1,78 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export type UserRole = '投保人' | '保险人'
export interface User {
id: string
name: string
email: string
role: UserRole
avatar?: string
}
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
const token = ref<string | null>(null)
const loading = ref(true)
const isAuthenticated = computed(() => !!user.value && !!token.value)
function restoreAuth() {
const savedToken = localStorage.getItem('rmo_token')
const savedUser = localStorage.getItem('rmo_user')
if (savedToken && savedUser) {
try {
token.value = savedToken
user.value = JSON.parse(savedUser)
} catch {
localStorage.removeItem('rmo_token')
localStorage.removeItem('rmo_user')
}
}
loading.value = false
}
async function login(username: string, password: string) {
loading.value = true
try {
await new Promise((r) => setTimeout(r, 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') {
user.value = mockData.user
token.value = mockData.token
localStorage.setItem('rmo_token', mockData.token)
localStorage.setItem('rmo_user', JSON.stringify(mockData.user))
} else {
throw new Error('用户名或密码错误')
}
} catch (e) {
throw e
} finally {
loading.value = false
}
}
function logout() {
user.value = null
token.value = null
localStorage.removeItem('rmo_token')
localStorage.removeItem('rmo_user')
}
return { user, token, loading, isAuthenticated, login, logout, restoreAuth }
})

16
src/stores/quoteModal.ts Normal file
View File

@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useQuoteModalStore = defineStore('quoteModal', () => {
const isOpen = ref(false)
function openQuoteModal() {
isOpen.value = true
}
function closeQuoteModal() {
isOpen.value = false
}
return { isOpen, openQuoteModal, closeQuoteModal }
})

View File

@ -0,0 +1,69 @@
<template>
<PageContainer>
<div class="about-overview">
<PageHeader title="RMO概述" description="生命科学风险管理平台" />
<div class="page-body">
<section class="section">
<div class="container">
<h2 class="section-title">关于RMO</h2>
<div class="content-grid">
<div class="content-card">
<h3>我们的使命</h3>
<p>RMO致力于为生命科学行业提供全方位的风险管理解决方案通过系统化的数据收集监测和分析识别潜在风险信号为风险管理决策提供科学依据</p>
</div>
<div class="content-card">
<h3>我们的愿景</h3>
<p>成为生命科学领域最具影响力的风险管理平台为临床试验和上市后药物安全提供专业高效的风险管理服务</p>
</div>
<div class="content-card">
<h3>核心价值</h3>
<ul>
<li>专业基于行业最佳实践和法规要求</li>
<li>高效系统化的流程和智能化的工具</li>
<li>可靠严格的质量控制和数据安全</li>
<li>创新持续的技术创新和服务优化</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="section-title">服务范围</h2>
<div class="services-grid">
<div class="service-item">
<div class="service-icon">📊</div>
<h3>药物警戒</h3>
<p>系统化的数据收集监测和分析服务</p>
</div>
<div class="service-item">
<div class="service-icon">🛡</div>
<h3>临床保险</h3>
<p>为临床试验提供全面的保险保障服务</p>
</div>
<div class="service-item">
<div class="service-icon">💼</div>
<h3>产品保险</h3>
<p>上市后药物安全与风险管理保障</p>
</div>
<div class="service-item">
<div class="service-icon">📚</div>
<h3>知识资源</h3>
<p>法规指南保险知识行业洞见</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/AboutOverview.css';
</style>

61
src/views/Contact.vue Normal file
View File

@ -0,0 +1,61 @@
<template>
<PageContainer>
<div class="contact-page">
<PageHeader title="联系我们" description="获取RMO最新资讯第一时间了解我们的企业动态" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="contact-grid">
<div class="contact-card">
<h3>客户服务中心</h3>
<p>邮箱clientservice@rmo.com</p>
<p>电话400-XXX-XXXX</p>
</div>
<div class="contact-card">
<h3>业务咨询/RFP</h3>
<p>邮箱marketing@rmo.com</p>
<p>电话400-XXX-XXXX</p>
</div>
<div class="contact-card">
<h3>媒体与投资者咨询</h3>
<p>邮箱PR@rmo.com</p>
<p>电话400-XXX-XXXX</p>
</div>
<div class="contact-card">
<h3>合规疑虑</h3>
<p>邮箱compliance@rmo.com</p>
<p>电话400-XXX-XXXX</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="section-title">订阅我们的通讯</h2>
<p class="section-description">马上注册RMO通讯简报第一时间获取我们的企业动态白皮书行业报告</p>
<form class="newsletter-form" @submit.prevent>
<div class="form-group">
<input type="email" placeholder="请输入您的邮箱地址" class="form-input" required />
<button type="submit" class="btn btn-primary">订阅</button>
</div>
<p class="form-note">
RMO承诺对您在本网页下提供的任何信息包括您的个人信息将按照相关的法律法规及RMO
<RouterLink to="/privacy-policy" class="privacy-link">隐私政策</RouterLink>
的规定进行严格保密
</p>
</form>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Contact.css';
</style>

View File

@ -0,0 +1,25 @@
<template>
<PageContainer>
<div class="risk-data-page">
<PageHeader variant="module" title="药安字典" description="药品安全相关术语与数据字典" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card main-card">
<p class="building-notice">建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RiskData.css';
</style>

69
src/views/FAQ.vue Normal file
View File

@ -0,0 +1,69 @@
<template>
<PageContainer>
<div class="faq">
<PageHeader title="常见问题" description="FAQ列表与问题解答" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>保险相关问题</h2>
<div class="faq-list">
<div class="faq-item">
<h3>Q: 保险保障范围包括哪些</h3>
<p>A: 保险主要保障SUSARSAE等首要风险包括身故残疾赔偿金等具体保障范围请参考保险合同条款</p>
</div>
<div class="faq-item">
<h3>Q: 理赔申请需要多长时间</h3>
<p>A: 正常情况下在收集到完整资料后7-10个工作日内完成详细评估5个工作日内提出赔偿方案建议</p>
</div>
<div class="faq-item">
<h3>Q: 如何申请理赔</h3>
<p>A: 您可以通过医院端申办者端或CRO报案具体流程请参考理赔服务流程页面</p>
</div>
</div>
<h2>保证方案问题</h2>
<div class="faq-list">
<div class="faq-item">
<h3>Q: 专项风险管理基金如何设立</h3>
<p>A: 申办者根据项目大小历史或行业经验向华泰经纪支付风险管理费例如一个项目3-5双方约定使用范围对象执行和审批流程等要素</p>
</div>
<div class="faq-item">
<h3>Q: 风险减量服务包括哪些内容</h3>
<p>A: 包括风险点检查人员培训方案完善建议等帮助主动识别和降低风险</p>
</div>
<div class="faq-item">
<h3>Q: 外溢风险管理服务的响应时间是多少</h3>
<p>A: 正常情况下24小时内与医院受试者沟通并通知申办者紧急案件立即响应</p>
</div>
</div>
<h2>服务流程问题</h2>
<div class="faq-list">
<div class="faq-item">
<h3>Q: 如何联系RMO服务团队</h3>
<p>A: 除日常的工作群微信群/钉钉群您可以通过邮件 rmo@vdanno.com 或电话 4009 606 520 联系我们</p>
</div>
<div class="faq-item">
<h3>Q: 服务响应时效如何</h3>
<p>A: 日常咨询24小时内回复紧急案件立即响应跨学科问题各方沟通明确后统一回复</p>
</div>
<div class="faq-item">
<h3>Q: 如何跟踪案件处理进展</h3>
<p>A: 您可以通过沟通群或电话咨询案件处理进展我们会及时向您通报案件处理状态</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/FAQ.css';
</style>

57
src/views/Holder.vue Normal file
View File

@ -0,0 +1,57 @@
<template>
<PageContainer>
<div class="holder">
<PageHeader title="持有人职责" description="负责上市后药物安全" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card">
<h2>持有人职责概述</h2>
<ul class="responsibility-overview">
<li>建立药物警戒体系开展上市后安全性监测</li>
<li>收集评价和报告药品不良反应</li>
<li>开展药品上市后研究持续评估风险与获益</li>
<li>制定并实施药品风险管理计划</li>
</ul>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card main-card">
<h2>持有人核心职责</h2>
<div class="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>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Holder.css';
</style>

160
src/views/Home.vue Normal file
View File

@ -0,0 +1,160 @@
<template>
<div class="home-fullscreen">
<div class="scroll-indicator">
<button
v-for="(_, index) in 5"
:key="index"
:class="['indicator-dot', { active: currentSection === index }]"
:aria-label="`Section ${index + 1}`"
@click="goToSection(index)"
/>
</div>
<section
v-for="(section, i) in sections"
:key="i"
:ref="(el: unknown) => { if (el) sectionsRef[i] = el as HTMLElement }"
:class="['home-section', section.class]"
:style="i === 0 ? heroStyle : {}"
>
<div v-if="i === 0" class="hero-content">
<h1 class="hero-title">赋能生命科学风险管理</h1>
<p class="hero-subtitle">为临床试验和上市后药物安全提供全方位的风险管理解决方案</p>
<div class="hero-buttons">
<RouterLink to="/about/overview" class="btn btn-primary">了解更多</RouterLink>
<button type="button" class="btn btn-secondary" @click="quoteModal.openQuoteModal">获取报价</button>
</div>
<div class="hero-scroll-hint">
<span>向下滚动</span>
<div class="scroll-arrow"></div>
</div>
</div>
<div v-else class="section-content">
<h2 class="section-title">{{ section.title }}</h2>
<p v-if="section.subtitle" class="section-subtitle">{{ section.subtitle }}</p>
<div v-if="i === 1" class="solutions-grid">
<RouterLink to="/solutions/pharmacovigilance" class="solution-card">
<div class="solution-icon">📊</div>
<h3>药物警戒</h3>
<p>系统化的数据收集监测和分析识别潜在风险信号</p>
<span class="solution-link">了解更多 </span>
</RouterLink>
<RouterLink to="/solutions/clinical-insurance" class="solution-card">
<div class="solution-icon">🛡</div>
<h3>临床保险</h3>
<p>为临床试验提供全面的保险保障服务</p>
<span class="solution-link">了解更多 </span>
</RouterLink>
<RouterLink to="/solutions/product-insurance" class="solution-card">
<div class="solution-icon">💼</div>
<h3>产品保险</h3>
<p>上市后药物安全与风险管理保障</p>
<span class="solution-link">了解更多 </span>
</RouterLink>
</div>
<div v-else-if="i === 2" class="capabilities-grid">
<div v-for="item in capabilities" :key="item.label" class="capability-item">
<div class="capability-number">{{ item.number }}</div>
<div class="capability-label">{{ item.label }}</div>
</div>
</div>
<div v-else-if="i === 3" class="knowledge-grid">
<RouterLink to="/knowledge/regulations" class="knowledge-card">
<h3>法规指南</h3>
<p>最新的法律法规与实践指南</p>
</RouterLink>
<RouterLink to="/knowledge/insurance" class="knowledge-card">
<h3>保险知识</h3>
<p>保险方案设计与风险管理知识</p>
</RouterLink>
<RouterLink to="/knowledge/pv-insurance" class="knowledge-card">
<h3>PV与保险</h3>
<p>药物警戒与保险的深度融合</p>
</RouterLink>
<RouterLink to="/faq" class="knowledge-card">
<h3>常见问题</h3>
<p>解答您关心的问题</p>
</RouterLink>
</div>
<div v-else-if="i === 4" class="contact-actions">
<RouterLink to="/contact" class="btn btn-primary">立即联系</RouterLink>
<RouterLink to="/about/overview" class="btn btn-secondary">关于RMO</RouterLink>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { useQuoteModalStore } from '@/stores/quoteModal'
const quoteModal = useQuoteModalStore()
const currentSection = ref(0)
const sectionsRef = ref<(HTMLElement | null)[]>([])
let observer: IntersectionObserver | null = null
const heroStyle = {
backgroundImage: "url('/pic/Home_Page.png')",
backgroundPosition: 'center center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundAttachment: 'fixed' as const
}
const sections = [
{ class: 'hero-section', title: '', subtitle: '' },
{ class: 'solutions-section', title: '一体化风险管理解决方案', subtitle: '提供全流程研发服务与解决方案' },
{ class: 'capabilities-section', title: '核心能力', subtitle: '' },
{ class: 'knowledge-section', title: '知识资源', subtitle: '分享我们的行业洞见和研发咨询' },
{ class: 'contact-section', title: '联系我们', subtitle: '获取RMO最新资讯第一时间了解我们的企业动态' }
]
const capabilities = [
{ number: '10,000+', label: '服务项目' },
{ number: '3,600+', label: '合作客户' },
{ number: '180+', label: '服务网络' },
{ number: '130+', label: '创新药项目' }
]
function goToSection(index: number) {
currentSection.value = index
sectionsRef.value[index]?.scrollIntoView({ behavior: 'smooth' })
}
function handleWheel(e: WheelEvent) {
e.preventDefault()
const delta = e.deltaY > 0 ? 1 : -1
const next = Math.max(0, Math.min(4, currentSection.value + delta))
if (next !== currentSection.value) {
currentSection.value = next
sectionsRef.value[next]?.scrollIntoView({ behavior: 'smooth' })
}
}
onMounted(() => {
window.addEventListener('wheel', handleWheel, { passive: false })
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const idx = sectionsRef.value.indexOf(entry.target as HTMLElement)
if (idx !== -1) currentSection.value = idx
}
})
},
{ threshold: 0.5 }
)
nextTick(() => {
sectionsRef.value.forEach((el) => el && observer?.observe(el))
})
})
onUnmounted(() => {
window.removeEventListener('wheel', handleWheel)
observer?.disconnect()
})
</script>
<style scoped>
@import '@/pages/Home.css';
</style>

49
src/views/Institution.vue Normal file
View File

@ -0,0 +1,49 @@
<template>
<PageContainer>
<div class="institution">
<PageHeader title="研究中心" description="为试验机构、研究者、伦理委员会提供专业支持" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card">
<h2>研究中心职责概述</h2>
<ul class="responsibility-overview">
<li>协助风险管理保障受试者安全</li>
<li>研究者负责临床试验的执行</li>
<li>伦理委员会负责伦理审查</li>
<li>及时报告不良事件</li>
</ul>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card main-card">
<h2>试验机构关注要点</h2>
<div class="content-section">
<h3>与受试者安全相关的内容</h3>
<ul>
<li> 受试者安全监测和报告机制</li>
<li> 不良事件的及时识别和处理</li>
<li> 医疗救治的及时性和有效性</li>
<li> 风险预警和应急响应</li>
</ul>
<h3>机构职责说明</h3>
<p>试验机构作为临床试验的执行主体需要协助申办者进行风险管理确保试验过程中受试者的安全机构应建立完善的安全监测体系及时报告不良事件配合RMO模式的风险管理服务</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Institution.css';
</style>

View File

@ -0,0 +1,36 @@
<template>
<PageContainer>
<div class="knowledge-page">
<PageHeader title="保险知识" description="保险方案设计与风险管理知识" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="knowledge-content">
<div class="knowledge-card">
<h3>保险方案设计</h3>
<p>了解如何设计适合临床试验的保险方案包括基础保障和全面保障</p>
</div>
<div class="knowledge-card">
<h3>风险管理</h3>
<p>学习风险管理的最佳实践降低临床试验风险</p>
</div>
<div class="knowledge-card">
<h3>理赔流程</h3>
<p>了解保险理赔的流程和要求确保及时获得保障</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/KnowledgeBase.css';
</style>

View File

@ -0,0 +1,36 @@
<template>
<PageContainer>
<div class="knowledge-page">
<PageHeader title="PV与保险" description="药物警戒与保险的深度融合" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="knowledge-content">
<div class="knowledge-card">
<h3>PV数据在保险中的应用</h3>
<p>如何利用药物警戒数据优化保险方案设计和风险评估</p>
</div>
<div class="knowledge-card">
<h3>风险信号识别</h3>
<p>通过PV系统识别风险信号为保险决策提供依据</p>
</div>
<div class="knowledge-card">
<h3>综合风险管理</h3>
<p>将PV与保险相结合形成完整的风险管理体系</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/KnowledgeBase.css';
</style>

View File

@ -0,0 +1,36 @@
<template>
<PageContainer>
<div class="knowledge-page">
<PageHeader title="法规指南" description="最新的法律法规与实践指南" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="knowledge-content">
<div class="knowledge-card">
<h3>法律法规</h3>
<p>临床试验相关的法律法规要求包括GCP药物管理法等</p>
</div>
<div class="knowledge-card">
<h3>实践指南</h3>
<p>行业最佳实践指南协助风险管理保障受试者安全</p>
</div>
<div class="knowledge-card">
<h3>行业动态</h3>
<p>最新的风险提示与行业信息及时了解行业变化</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/KnowledgeBase.css';
</style>

96
src/views/Login.vue Normal file
View File

@ -0,0 +1,96 @@
<template>
<PageContainer>
<div class="login">
<PageHeader title="登录" description="登录后将根据角色身份进入保险报价与理赔页面" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="login-card card main-card">
<h2>用户登录</h2>
<form class="login-form" @submit.prevent="handleSubmit">
<div v-if="error" class="error-message">{{ error }}</div>
<div class="form-group">
<label for="username">用户名/邮箱</label>
<input
id="username"
v-model="username"
type="text"
placeholder="请输入用户名或邮箱"
required
:disabled="loading"
/>
<div class="form-hint">
<small>测试账号admin, policyholder, insurer密码123456</small>
</div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
id="password"
v-model="password"
type="password"
placeholder="请输入密码"
required
:disabled="loading"
/>
</div>
<div class="form-options">
<label class="checkbox-label">
<input type="checkbox" />
<span>记住我</span>
</label>
<a href="#" class="forgot-link">忘记密码</a>
</div>
<button type="submit" class="btn btn-primary" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
</form>
<div class="login-footer">
<p>还没有账号请联系管理员创建账号</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const router = useRouter()
const route = useRoute()
const auth = useAuthStore()
const username = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)
watch(() => auth.isAuthenticated, (isAuth) => {
if (isAuth) router.replace('/dashboard')
}, { immediate: true })
async function handleSubmit() {
error.value = ''
loading.value = true
try {
await auth.login(username.value, password.value)
const from = (route.query.from as string) || '/dashboard'
router.replace(from)
} catch (err) {
error.value = err instanceof Error ? err.message : '登录失败,请检查用户名和密码'
} finally {
loading.value = false
}
}
</script>
<style scoped>
@import '@/pages/Login.css';
</style>

25
src/views/Overseas.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<PageContainer>
<div class="overseas module-dashboard-container">
<PageHeader variant="module" title="海外风险" description="跨境临床试验与海外市场的风险管理保险与保障" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card main-card">
<p class="building-notice">建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Overseas.css';
</style>

25
src/views/PVReport.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<PageContainer>
<div class="risk-data-page">
<PageHeader variant="module" title="PV报告" description="药物警戒报告与数据分析" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card main-card">
<p class="building-notice">建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RiskData.css';
</style>

48
src/views/Participant.vue Normal file
View File

@ -0,0 +1,48 @@
<template>
<PageContainer>
<div class="participant">
<PageHeader title="受试者专区" description="了解临床试验,保障您的权益" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card">
<h2>受试者专区概述</h2>
<ul class="responsibility-overview">
<li>了解临床试验基本知识</li>
<li>知情同意与权益保障</li>
<li>损害救济与补偿机制</li>
</ul>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card main-card">
<h2>临床试验介绍</h2>
<div class="content-section">
<h3>什么是临床试验</h3>
<p>临床试验是指在人体病人或健康志愿者进行的系统性研究以证实或揭示试验药物的作用不良反应及/或试验药物的吸收分布代谢和排泄目的是确定试验药物的疗效与安全性</p>
<h3>参与临床试验的意义</h3>
<ul>
<li> 为医学进步做出贡献</li>
<li> 可能获得新的治疗机会</li>
<li> 获得专业的医疗监测和照护</li>
<li> 帮助更多患者获得更好的治疗</li>
</ul>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Participant.css';
</style>

25
src/views/PostMarket.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<PageContainer>
<div class="post-market module-dashboard-container">
<PageHeader variant="module" title="上市应用" description="药品上市后风险管理与药物警戒的保险与保障方案" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card main-card">
<p class="building-notice">建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/PostMarket.css';
</style>

View File

@ -0,0 +1,52 @@
<template>
<PageContainer>
<div class="privacy-policy-page">
<PageHeader title="隐私政策" description="上海达诺新晨信息科技有限公司隐私政策" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="privacy-content card main-card">
<div class="privacy-meta">
<p><strong>发布日期</strong>2025 2 5 </p>
<p><strong>生效日期</strong>2025 2 5 </p>
</div>
<div class="privacy-intro">
<p>上海达诺新晨信息科技有限公司以下简称 "达诺" "我们"负责处理您在本网站上的个人信息</p>
<p>请仔细阅读本隐私政策其中描述了我们收集有关访问本网站的个人信息的方式"个人数据"我们如何收集使用存储对外提供保护您的个人信息及您享有何种权利</p>
<p>本隐私政策自 2025 2 5 日起生效除非本隐私政策的内容有所更新其内容将持续有效</p>
</div>
<div class="privacy-section">
<h2>1. 用户</h2>
<p>通过使用我们的网站您同意并接受向达诺提供能直接或间接识别您身份的信息以下简称 "个人信息" "个人数据"并将这类信息收集处理和存储在中国境内</p>
</div>
<div class="privacy-section">
<h2>2. 我们收集哪些您的个人信息</h2>
<p>达诺收集并处理如下您的个人信息通过联系我们版块收集姓名电子邮件联系电话等信息通过合规疑虑版块收集您的疑虑反馈通过邮箱电话等书面形式联系时保留您的相关信息</p>
</div>
<div class="privacy-section">
<h2>13. 联系我们</h2>
<p>如果您对本隐私政策或者达诺的数据保护有任何疑问意见或建议通过以下方式与我们联系</p>
<div class="contact-info">
<p><strong>邮箱</strong><a href="mailto:RMO@vdano.com">RMO@vdano.com</a></p>
<p><strong>公司</strong>上海达诺新晨信息科技有限公司简称达诺</p>
</div>
</div>
<div class="privacy-footer">
<p>上海达诺新晨信息科技有限公司</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/PrivacyPolicy.css';
</style>

View File

@ -0,0 +1,22 @@
<template>
<ResourceCenterOverview v-if="isOverview" />
<ResourceCenterLaws v-else-if="route.path.includes('/laws')" />
<ResourceCenterPracticeGuide v-else-if="route.path.includes('/practice-guide')" />
<ResourceCenterTraining v-else-if="route.path.includes('/training')" />
<ResourceCenterOverview v-else />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import ResourceCenterOverview from './ResourceCenterOverview.vue'
import ResourceCenterLaws from './ResourceCenterLaws.vue'
import ResourceCenterPracticeGuide from './ResourceCenterPracticeGuide.vue'
import ResourceCenterTraining from './ResourceCenterTraining.vue'
const route = useRoute()
const isOverview = computed(() =>
route.path === '/system-management' || route.path === '/system-management/'
)
</script>

View File

@ -0,0 +1,44 @@
<template>
<PageContainer>
<div class="resource-center">
<PageHeader title="法律法规" description="临床试验与风险管理相关法律法规" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>法律法规</h2>
<p>与临床试验风险主体受试者保护及保险保证相关的法律法规与规范性文件</p>
<div class="guide-grid">
<div class="guide-item">
<h3>📜 GCP 与受试者保护</h3>
<p>药物临床试验质量管理规范等与临床试验及受试者保护相关的法规要求</p>
</div>
<div class="guide-item">
<h3>📜 申办者责任</h3>
<p>申办者应向研究者和临床试验机构提供法律上经济上的保险或保证</p>
</div>
<div class="guide-item">
<h3>📜 药品管理相关</h3>
<p>药品管理法等对持有人责任与风险管理能力的要求</p>
</div>
<div class="guide-item">
<h3>📜 保险与保证</h3>
<p>与保险条款理赔及保证方式相关的法规与行业规范</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/ResourceCenter.css';
</style>

View File

@ -0,0 +1,54 @@
<template>
<PageContainer>
<div class="resource-center module-dashboard-container">
<PageHeader variant="module" title="资源中心" description="提供实践指南、培训材料和常见问题等资源支持" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card">
<h2>关于资源中心</h2>
<p>资源中心为您提供全面的实践指南培训材料和常见问题等资源帮助您更好地理解和使用RMO服务</p>
<p>无论您是申办者研究中心还是服务方都可以在这里找到适合您的实践指南培训资源和常见问题解答</p>
</div>
</div>
</section>
<section class="section nav-section">
<div class="container">
<h2 class="section-title">资源中心</h2>
<div class="nav-grid">
<RouterLink to="/system-management/laws" class="nav-card">
<div class="nav-icon">📜</div>
<h3>法律法规</h3>
<p>临床试验与风险管理相关法律法规</p>
</RouterLink>
<RouterLink to="/system-management/practice-guide" class="nav-card">
<div class="nav-icon">📋</div>
<h3>实践指南</h3>
<p>操作指南文档与最佳实践案例</p>
</RouterLink>
<RouterLink to="/system-management/training" class="nav-card">
<div class="nav-icon">🎓</div>
<h3>培训材料</h3>
<p>培训视频文档和课程资源</p>
</RouterLink>
<RouterLink to="/system-management/faq" class="nav-card">
<div class="nav-icon"></div>
<h3>常见问题</h3>
<p>保险保证与流程相关FAQ</p>
</RouterLink>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/ResourceCenter.css';
</style>

View File

@ -0,0 +1,44 @@
<template>
<PageContainer>
<div class="resource-center">
<PageHeader title="实践指南" description="操作指南文档与最佳实践案例" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>操作指南</h2>
<p>提供详细的操作指南文档帮助您更好地使用RMO服务</p>
<div class="guide-grid">
<div class="guide-item">
<h3>📋 理赔流程指南</h3>
<p>详细的理赔申请流程说明</p>
</div>
<div class="guide-item">
<h3>📝 项目启动指南</h3>
<p>项目启动时的保险投保和基金设立流程指南</p>
</div>
<div class="guide-item">
<h3>🔍 风险评估指南</h3>
<p>如何识别和评估临床试验风险的方法和工具</p>
</div>
<div class="guide-item">
<h3>📊 报告提交指南</h3>
<p>各类报告和文档的提交要求和格式规范</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/ResourceCenter.css';
</style>

View File

@ -0,0 +1,43 @@
<template>
<PageContainer>
<div class="resource-center">
<PageHeader title="培训材料" description="培训视频、文档和课程资源" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>培训视频</h2>
<div class="training-grid">
<div class="training-item">
<h3>🎥 RMO模式介绍</h3>
<p>全面介绍RMO模式的核心概念和服务内容</p>
</div>
<div class="training-item">
<h3>🎥 理赔流程培训</h3>
<p>详细的理赔申请流程和操作培训视频</p>
</div>
<div class="training-item">
<h3>🎥 CRC培训课程</h3>
<p>针对临床研究协调员的专业培训课程</p>
</div>
<div class="training-item">
<h3>🎥 风险管理培训</h3>
<p>临床试验风险识别和管理方法培训</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/ResourceCenter.css';
</style>

View File

@ -0,0 +1,112 @@
<template>
<PageContainer>
<div class="risk-duties-overview module-dashboard-container">
<PageHeader variant="module" title="风险职责" description="为临床试验各方提供专业的风险管理支持" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card">
<h2>关于风险职责</h2>
<p>在临床试验中不同角色承担着不同的职责和关注点RMO为各方提供针对性的风险管理支持确保临床试验过程中受试者的安全同时帮助各方更好地履行其职责</p>
<p>我们为申办者研究中心CXOCROCDMOSMO等各方提供专业的风险管理服务帮助各方识别风险评估风险管理风险确保临床试验的顺利进行</p>
</div>
</div>
</section>
<section class="section nav-section">
<div class="container">
<h2 class="section-title">各方职责</h2>
<div class="nav-grid">
<RouterLink to="/sponsor" class="nav-card">
<div class="nav-icon">💼</div>
<h3>申办者职责</h3>
<p>风险管理体系</p>
</RouterLink>
<RouterLink to="/holder" class="nav-card">
<div class="nav-icon">📋</div>
<h3>持有人职责</h3>
<p>负责上市后药物安全</p>
</RouterLink>
<RouterLink to="/participant" class="nav-card">
<div class="nav-icon">👤</div>
<h3>受试者专区</h3>
<p>临床试验介绍权益保障损害救济</p>
</RouterLink>
<RouterLink to="/institution" class="nav-card">
<div class="nav-icon">🏥</div>
<h3>研究中心</h3>
<p>机构研究者伦理委员会支持</p>
</RouterLink>
<RouterLink to="/service-provider" class="nav-card">
<div class="nav-icon">🤝</div>
<h3>CXO职责</h3>
<p>CROCDMOSMO支持服务</p>
</RouterLink>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card">
<h2>各方职责概述</h2>
<div class="responsibility-grid">
<div class="responsibility-item">
<h3>申办者</h3>
<ul>
<li> 承担受试者保护的主要责任</li>
<li> 提供法律上经济上的保险或保证</li>
<li> 建立完善的风险管理体系</li>
<li> 确保临床试验的质量和安全</li>
</ul>
</div>
<div class="responsibility-item">
<h3>持有人</h3>
<ul>
<li> 建立药物警戒体系开展上市后安全性监测</li>
<li> 收集评价和报告药品不良反应</li>
<li> 开展药品上市后研究持续评估风险与获益</li>
<li> 制定并实施药品风险管理计划</li>
</ul>
</div>
<div class="responsibility-item">
<h3>研究中心</h3>
<ul>
<li> 协助风险管理保障受试者安全</li>
<li> 研究者负责临床试验的执行</li>
<li> 伦理委员会负责伦理审查</li>
<li> 及时报告不良事件</li>
</ul>
</div>
<div class="responsibility-item">
<h3>受试者</h3>
<ul>
<li> 了解临床试验基本知识</li>
<li> 知情同意与权益保障</li>
<li> 损害救济与补偿机制</li>
</ul>
</div>
<div class="responsibility-item">
<h3>CXO服务方</h3>
<ul>
<li> CRO协助申办者进行临床试验</li>
<li> CDMO提供药物开发和生产服务</li>
<li> SMO提供现场管理服务</li>
<li> 协助风险管理流程的执行</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RiskDutiesOverview.css';
</style>

23
src/views/RmoMode.vue Normal file
View File

@ -0,0 +1,23 @@
<template>
<RmoModeOverview v-if="isOverview" />
<RmoModeInsurance v-else-if="route.path.includes('/insurance')" />
<RmoModeGuarantee v-else-if="route.path.includes('/guarantee')" />
<RmoModeInsuranceGuarantee v-else-if="route.path.includes('/insurance-guarantee')" />
<RmoModeOverview v-else />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import RmoModeOverview from './RmoModeOverview.vue'
import RmoModeInsurance from './RmoModeInsurance.vue'
import RmoModeGuarantee from './RmoModeGuarantee.vue'
import RmoModeInsuranceGuarantee from './RmoModeInsuranceGuarantee.vue'
const route = useRoute()
const isOverview = computed(() => {
const p = route.path
return p === '/rmo-mode' || p === '/rmo-mode/'
})
</script>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="rmo-mode">
<PageHeader title="保证方案" description="专项基金、风险减量、外溢风险管理" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>保证方案</h2>
<p>专项风险管理基金风险减量服务外溢风险管理服务</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RmoMode.css';
</style>

View File

@ -0,0 +1,56 @@
<template>
<PageContainer>
<div class="rmo-mode">
<PageHeader title="保险方案" description="为申办者提供全面的保险保障服务" />
<div class="page-body">
<section class="section section-get-quotes">
<div class="container">
<div class="get-quotes-card card main-card">
<h3 class="get-quotes-title">获取报价</h3>
<p class="get-quotes-desc">
填写项目方案编号项目标题申办者项目分期可手动填写或上传项目方案由 AI 识别生成报价后可向各保司获取精准报价
</p>
<div class="get-quotes-actions">
<button type="button" class="btn btn-primary get-quotes-btn" @click="quoteModal.openQuoteModal">
获取报价
</button>
<button
v-if="auth.isAuthenticated"
type="button"
class="btn btn-secondary get-quotes-btn"
@click="router.push('/dashboard/project-quotes')"
>
前往报价页面
</button>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card main-card">
<h2>保险方案</h2>
<p>基础保障全面保障保险团体标准核心内容等</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useQuoteModalStore } from '@/stores/quoteModal'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const router = useRouter()
const auth = useAuthStore()
const quoteModal = useQuoteModalStore()
</script>
<style scoped>
@import '@/pages/RmoMode.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="rmo-mode">
<PageHeader title="保险与保证" description="保险与保证结合,形成完整保障体系" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>保险与保证</h2>
<p>保险方案与保证方案结合形成完整的风险管理保障体系</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RmoMode.css';
</style>

View File

@ -0,0 +1,65 @@
<template>
<PageContainer>
<div class="rmo-mode module-dashboard-container">
<PageHeader variant="module" title="临床试验:首先是保护受试者安全" />
<div class="module-content">
<section class="section section-why-rm">
<div class="container">
<h2 class="section-title">风险管理目的在于保护患者安全</h2>
<div class="why-rm-images">
<div class="why-rm-image-wrap">
<img src="/pic/why%20risk%20management.png" alt="风险管理" class="why-rm-image" />
</div>
</div>
</div>
</section>
<section class="section section-closed-loop">
<div class="container">
<h2 class="section-title">风险管理从安全数据到患者救治的闭环</h2>
<div class="closed-loop-layout">
<div class="closed-loop-left">
<h3 class="closed-loop-subtitle">药物警戒数据业务逻辑</h3>
<div class="closed-loop-image-wrap">
<img src="/pic/PV%20Model.png" alt="药物警戒" class="closed-loop-image" />
</div>
</div>
<div class="closed-loop-arrow" aria-hidden="true">
<span class="closed-loop-arrow-icon"></span>
<span class="closed-loop-arrow-label">数据与行动</span>
</div>
<div class="closed-loop-right">
<h3 class="closed-loop-subtitle">风险管理措施保障患者及时救治</h3>
<div class="closed-loop-measures">
<RouterLink to="/rmo-mode/insurance" class="closed-loop-measure-card">
<span class="closed-loop-measure-icon">🛡</span>
<h4>保险方案</h4>
<p>SUSARSAE 等首要风险保障合同审查理赔医疗直付</p>
</RouterLink>
<RouterLink to="/rmo-mode/guarantee" class="closed-loop-measure-card">
<span class="closed-loop-measure-icon">💰</span>
<h4>保证方案</h4>
<p>专项基金风险减量外溢风险管理</p>
</RouterLink>
<RouterLink to="/rmo-mode/insurance-guarantee" class="closed-loop-measure-card">
<span class="closed-loop-measure-icon">🔄</span>
<h4>保险与保证</h4>
<p>保险与保证结合形成完整保障体系</p>
</RouterLink>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RmoMode.css';
</style>

View File

@ -0,0 +1,41 @@
<template>
<PageContainer>
<div class="service-provider">
<PageHeader title="CXO职责" description="为CRO、CDMO、SMO提供专业支持" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card">
<h2>CXO职责概述</h2>
<ul class="responsibility-overview">
<li>CRO协助申办者进行临床试验</li>
<li>CDMO提供药物开发和生产服务</li>
<li>SMO提供现场管理服务</li>
<li>协助风险管理流程的执行</li>
</ul>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card main-card">
<h2>CRO支持</h2>
<div class="content-section">
<p>CROContract Research Organization合同研究组织作为临床试验的重要服务提供方RMO模式可以为CRO提供全面的风险管理支持帮助CRO更好地履行合同义务保障临床试验的顺利进行</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/ServiceProvider.css';
</style>

View File

@ -0,0 +1,25 @@
<template>
<PageContainer>
<div class="risk-data-page">
<PageHeader variant="module" title="智能获取" description="风险数据智能获取与整合服务" />
<div class="module-content">
<section class="section">
<div class="container">
<div class="card main-card">
<p class="building-notice">建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/RiskData.css';
</style>

View File

@ -0,0 +1,40 @@
<template>
<PageContainer>
<div class="solutions-page">
<PageHeader title="临床保险" description="为临床试验提供全面的保险保障服务" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="solutions-nav">
<RouterLink to="/rmo-mode/insurance" class="solution-nav-link">保险方案</RouterLink>
<RouterLink to="/rmo-mode/guarantee" class="solution-nav-link">保证设计</RouterLink>
</div>
<div class="solutions-content">
<div class="solution-item">
<h3>基础保障</h3>
<p>满足基础合规要求的保险保障方案</p>
</div>
<div class="solution-item">
<h3>全面保障</h3>
<p>扩大保障范围优化理赔流程与医疗直付等服务</p>
</div>
<div class="solution-item">
<h3>风险减量</h3>
<p>提供风险减量相关服务降低风险发生概率</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Solutions.css';
</style>

View File

@ -0,0 +1,44 @@
<template>
<PageContainer>
<div class="solutions-page">
<PageHeader title="药物警戒" description="系统化的数据收集、监测和分析,识别潜在风险信号" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="solutions-nav">
<RouterLink to="/risk-data/pv-service" class="solution-nav-link">PV服务</RouterLink>
<RouterLink to="/risk-data/ai-tools" class="solution-nav-link">AI工具</RouterLink>
</div>
<div class="solutions-content">
<div class="solution-item">
<h3>数据收集与整合</h3>
<p>多渠道数据源整合建立统一的风险数据库</p>
</div>
<div class="solution-item">
<h3>信号检测</h3>
<p>运用统计学方法和数据挖掘技术及时发现异常信号</p>
</div>
<div class="solution-item">
<h3>风险分析</h3>
<p>深入分析风险特征发生频率和严重程度</p>
</div>
<div class="solution-item">
<h3>趋势评估</h3>
<p>持续监测风险变化趋势预测潜在风险</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Solutions.css';
</style>

View File

@ -0,0 +1,40 @@
<template>
<PageContainer>
<div class="solutions-page">
<PageHeader title="产品责任" description="上市后药物安全与风险管理保障" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="solutions-nav">
<RouterLink to="/post-market/insurance" class="solution-nav-link">保险方案</RouterLink>
<RouterLink to="/post-market/guarantee" class="solution-nav-link">保证设计</RouterLink>
</div>
<div class="solutions-content">
<div class="solution-item">
<h3>上市后监测</h3>
<p>持续监测上市后药物的安全性</p>
</div>
<div class="solution-item">
<h3>风险管理</h3>
<p>系统化的风险管理措施和保障方案</p>
</div>
<div class="solution-item">
<h3>合规保障</h3>
<p>确保符合上市后监管要求</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Solutions.css';
</style>

126
src/views/Sponsor.vue Normal file
View File

@ -0,0 +1,126 @@
<template>
<PageContainer>
<div class="sponsor">
<PageHeader title="申办者职责" description="全面的风险管理体系" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card">
<h2>申办者职责概述</h2>
<ul class="responsibility-overview">
<li>承担受试者保护的主要责任</li>
<li>提供法律上经济上的保险或保证</li>
<li>建立完善的风险管理体系</li>
<li>确保临床试验的质量和安全</li>
</ul>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="card main-card">
<h2>风险管理体系</h2>
<div class="risk-section">
<h3>风险识别</h3>
<div class="risk-categories">
<div class="risk-category">
<h4>从临床试验药物角度</h4>
<ul>
<li><strong>AE不良事件</strong>为证明与药物相关</li>
<li><strong>ADR药物不良反应</strong>已证明与药物相关
<ul>
<li>SADR严重药物不良反应严重的ADR</li>
<li>ADR写入IB的RSI参考安全信息</li>
</ul>
</li>
<li><strong>SUSAR可疑且非预期的严重不良反应</strong>
<ul>
<li>新发现的严重不良反应需及时报告</li>
<li>未写入RSI的严重不良反应属于SUSAR</li>
</ul>
</li>
</ul>
</div>
<div class="risk-category">
<h4>从临床试验操作角度</h4>
<ul>
<li>临床试验方案合理性</li>
<li>临床试验方案操作是否符合规定</li>
<li>医疗行为是否合理</li>
<li>临床试验组织管理是否合理</li>
</ul>
</div>
<div class="risk-category">
<h4>其他相关风险</h4>
<ul>
<li>行为原则</li>
<li>心理原因</li>
<li>其他</li>
</ul>
</div>
</div>
</div>
<div class="risk-section">
<h3>风险评估</h3>
<p>华泰经纪协助申办者基于项目复杂度和风险度厘清首要风险与次要风险通过专业的风险评估工具和方法为项目制定合适的风险管理策略</p>
</div>
<div class="risk-section">
<h3>风险管理策略</h3>
<div class="strategy-grid">
<div class="strategy-card">
<h4>自留策略</h4>
<p>对于可接受的风险采用自留方式管理</p>
</div>
<div class="strategy-card">
<h4>保险策略</h4>
<p>对于首要风险通过保险进行风险转移</p>
</div>
<div class="strategy-card">
<h4>合同策略</h4>
<p>通过合同约定明确各方责任和风险分担</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="section process-section">
<div class="container">
<h2 class="section-title">操作流程</h2>
<div class="process-steps">
<div class="process-step">
<div class="step-number">1</div>
<h3>项目风险评估</h3>
<p>华泰经纪协助申办者基于项目复杂度和风险度厘清首要风险与次要风险</p>
</div>
<div class="process-step">
<div class="step-number">2</div>
<h3>投保流程</h3>
<p>通过华泰保险经纪完成首要风险投保</p>
</div>
<div class="process-step">
<div class="step-number">3</div>
<h3>专项风险管理基金设立</h3>
<p>申办者支付风险管理费设立专项风险管理基金3-5/项目</p>
</div>
<div class="process-step">
<div class="step-number">4</div>
<h3>理赔/补偿流程</h3>
<p>受试者出险后24小时内响应快速处理理赔和补偿</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/Sponsor.css';
</style>

View File

@ -0,0 +1,29 @@
<template>
<PageContainer>
<div class="claim-detail-page">
<PageHeader title="理赔详情" :description="`理赔ID: ${route.params.id}`" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>理赔详情</h2>
<p>理赔 ID: {{ route.params.id }}</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const route = useRoute()
</script>
<style scoped>
@import '@/pages/dashboard/ClaimDetail.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="claim-progress-page">
<PageHeader title="理赔进度" description="查看理赔进度" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>理赔进度</h2>
<p>理赔列表与进度跟踪</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/ClaimProgress.css';
</style>

View File

@ -0,0 +1,140 @@
<template>
<PageContainer>
<div class="dashboard-page">
<PageHeader
:title="`欢迎回来,${auth.user?.name || '用户'}`"
description="这里是您的工作台,可以快速查看保障、项目和待办任务"
/>
<div class="page-body">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">💼</div>
<div class="stat-content">
<h3>询价项目</h3>
<div class="stat-value">{{ data.inquiryProjects }}</div>
<div class="stat-details"><span>待处理询价项目</span></div>
</div>
<RouterLink :to="isPolicyholder ? '/dashboard/projects' : '/dashboard/inquiries'" class="stat-link">查看详情 </RouterLink>
</div>
<div class="stat-card">
<div class="stat-icon">🛡</div>
<div class="stat-content">
<h3>生效保障</h3>
<div class="stat-value">{{ data.activeCoverage }}</div>
<div class="stat-details"><span>已生效保障数量</span></div>
</div>
<RouterLink :to="isPolicyholder ? '/dashboard/projects' : '/dashboard/inquiries'" class="stat-link">查看详情 </RouterLink>
</div>
<div class="stat-card">
<div class="stat-icon">📋</div>
<div class="stat-content">
<h3>全部项目</h3>
<div class="stat-value">{{ data.allProjects }}</div>
<div class="stat-details"><span>包括生效中及已失效的项目</span></div>
</div>
<RouterLink :to="isPolicyholder ? '/dashboard/projects' : '/dashboard/inquiries'" class="stat-link">查看详情 </RouterLink>
</div>
</div>
<section v-if="isPolicyholder" class="section">
<div class="container">
<h2 class="section-title">快捷方式</h2>
<div class="quick-actions">
<button type="button" class="quick-action-card quick-action-button" @click="quoteModal.openQuoteModal">
<div class="action-icon">💼</div>
<h3>获取报价</h3>
<p>填写资料生成报价获取精准报价</p>
</button>
<RouterLink to="/dashboard/project-quotes" class="quick-action-card">
<div class="action-icon">📋</div>
<h3>报价页面</h3>
<p>查看全部保司报价与询价记录</p>
</RouterLink>
<RouterLink to="/dashboard/claims" class="quick-action-card">
<div class="action-icon">📄</div>
<h3>申请理赔</h3>
<p>跳转到理赔申请页面需关联项目</p>
</RouterLink>
<RouterLink to="/dashboard/projects" class="quick-action-card">
<div class="action-icon">📑</div>
<h3>方案介绍</h3>
<p>申请设计并介绍方案</p>
</RouterLink>
<RouterLink to="/dashboard/projects" class="quick-action-card">
<div class="action-icon">🎓</div>
<h3>培训支持</h3>
<p>申请培训支持</p>
</RouterLink>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="section-title">待处理任务</h2>
<div class="tasks-list card main-card">
<ul v-if="data.tasks.length" class="task-items">
<li v-for="task in data.tasks" :key="task.id" class="task-item">
<div class="task-info">
<h4 class="task-title">{{ task.title }}</h4>
<div class="task-meta">
<span class="task-type">{{ task.type }}</span>
<span class="task-time">{{ task.time }}</span>
</div>
</div>
<div class="task-actions">
<span :class="['task-priority', `priority-${task.priority}`]">
{{ task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低' }}
</span>
<RouterLink :to="task.link" class="btn btn-sm">处理</RouterLink>
</div>
</li>
</ul>
<div v-else class="empty-state">
<p>暂无待办任务</p>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { useQuoteModalStore } from '@/stores/quoteModal'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const auth = useAuthStore()
const quoteModal = useQuoteModalStore()
const isPolicyholder = computed(() => auth.user?.role === '投保人')
const isInsurer = computed(() => auth.user?.role === '保险人')
const policyholderData = {
inquiryProjects: 5,
activeCoverage: 8,
allProjects: 15,
tasks: [
{ id: '1', title: '待处理的报价项目', type: '报价', time: '2025-02-01 10:30', priority: 'high', link: '/dashboard/project-quotes' },
{ id: '2', title: '待处理的理赔', type: '理赔', time: '2025-02-01 09:15', priority: 'medium', link: '/dashboard/claims' }
]
}
const insurerData = {
inquiryProjects: 12,
activeCoverage: 20,
allProjects: 35,
tasks: [
{ id: '1', title: '待处理的报价项目', type: '报价', time: '2025-02-01 10:30', priority: 'high', link: '/dashboard/inquiries' },
{ id: '2', title: '待处理的理赔', type: '理赔', time: '2025-02-01 09:15', priority: 'medium', link: '/dashboard/claims' }
]
}
const data = computed(() => (isPolicyholder.value ? policyholderData : isInsurer.value ? insurerData : policyholderData))
</script>
<style scoped>
@import '@/pages/dashboard/Dashboard.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="drug-safety-page">
<PageHeader title="药安查" description="药物安全数据查询,不良反应与警戒信息检索" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>药安查</h2>
<p>建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/DrugSafetyQuery.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="icf-editor-page">
<PageHeader title="ICF智能修改" description="智能辅助修改知情同意书ICF内容" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>ICF智能修改</h2>
<p>建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/ICFEditor.css';
</style>

View File

@ -0,0 +1,29 @@
<template>
<PageContainer>
<div class="inquiry-detail-page">
<PageHeader title="询价详情" :description="`询价ID: ${route.params.id}`" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>询价详情</h2>
<p>询价 ID: {{ route.params.id }}</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const route = useRoute()
</script>
<style scoped>
@import '@/pages/dashboard/InquiryDetail.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="inquiry-list-page">
<PageHeader title="询价列表" description="查看询价列表" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>询价列表</h2>
<p>保险人可见的询价列表</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/InquiryList.css';
</style>

View File

@ -0,0 +1,76 @@
<template>
<PageContainer>
<div class="premium-calculator-page">
<PageHeader title="保费测算工具" description="根据项目信息、风险等级等参数计算保费" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="calculator-card card main-card">
<form class="calculator-form" @submit.prevent="handleSubmit">
<div class="form-group">
<label for="premium-projectType">项目类型</label>
<select id="premium-projectType" v-model="formData.projectType" required>
<option value="">请选择</option>
<option value="phase1">I期临床试验</option>
<option value="phase2">II期临床试验</option>
<option value="phase3">III期临床试验</option>
<option value="phase4">IV期临床试验</option>
</select>
</div>
<div class="form-group">
<label for="premium-riskLevel">风险等级</label>
<select id="premium-riskLevel" v-model="formData.riskLevel" required>
<option value="">请选择</option>
<option value="low">低风险</option>
<option value="medium">中风险</option>
<option value="high">高风险</option>
</select>
</div>
<div class="form-group">
<label for="premium-coverageAmount">保障金额</label>
<input id="premium-coverageAmount" v-model="formData.coverageAmount" type="number" placeholder="请输入保障金额" required />
</div>
<div class="form-group">
<label for="premium-participantCount">受试者人数</label>
<input id="premium-participantCount" v-model="formData.participantCount" type="number" placeholder="请输入受试者人数" required />
</div>
<div class="form-group">
<label for="premium-duration">试验周期</label>
<input id="premium-duration" v-model="formData.duration" type="number" placeholder="请输入试验周期" required />
</div>
<button type="submit" class="btn btn-primary">计算保费</button>
</form>
<div v-if="result !== null" class="calculator-result">
<h3>预估保费{{ result }} </h3>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const formData = ref({
projectType: '',
riskLevel: '',
coverageAmount: '',
participantCount: '',
duration: ''
})
const result = ref<number | null>(null)
function handleSubmit() {
result.value = Math.floor(Math.random() * 500000) + 100000
}
</script>
<style scoped>
@import '@/pages/dashboard/PremiumCalculator.css';
</style>

View File

@ -0,0 +1,29 @@
<template>
<PageContainer>
<div class="project-detail-page">
<PageHeader title="项目详情" :description="`项目ID: ${route.params.id}`" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>项目详情</h2>
<p>项目 ID: {{ route.params.id }}</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const route = useRoute()
</script>
<style scoped>
@import '@/pages/dashboard/ProjectDetail.css';
</style>

View File

@ -0,0 +1,49 @@
<template>
<PageContainer>
<div class="project-list-page">
<PageHeader title="项目列表" description="查看您的项目列表" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>项目列表</h2>
<div class="project-list">
<div v-for="p in mockProjects" :key="p.id" class="project-item">
<RouterLink :to="`/dashboard/projects/${p.id}`">
{{ p.projectNumber }} - {{ p.title }} ({{ p.status }})
</RouterLink>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const router = useRouter()
const auth = useAuthStore()
onMounted(() => {
if (auth.user?.role !== '投保人') {
router.replace('/dashboard')
}
})
const mockProjects = [
{ id: '1', projectNumber: 'CT-2025-001', title: 'XX药物III期临床试验', status: '已承保' },
{ id: '2', projectNumber: 'CT-2025-002', title: 'YY生物制品II期临床试验', status: '待审核' }
]
</script>
<style scoped>
@import '@/pages/dashboard/ProjectList.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="project-quotes-page">
<PageHeader title="项目报价" description="查看各保司报价" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>项目报价</h2>
<p>各保司报价与询价记录</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/ProjectQuotes.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="protocol-risk-page">
<PageHeader title="方案风险评估" description="借助 AI 评估方案信息不足风险与偏倚风险" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>方案风险评估</h2>
<p>建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/ProtocolRiskAssessment.css';
</style>

View File

@ -0,0 +1,26 @@
<template>
<PageContainer>
<div class="risk-scoring-page">
<PageHeader title="方案风险评分" description="对试验方案进行风险评分" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="card main-card">
<h2>方案风险评分</h2>
<p>建设中</p>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
</script>
<style scoped>
@import '@/pages/dashboard/RiskScoring.css';
</style>

View File

@ -0,0 +1,59 @@
<template>
<PageContainer>
<div class="tools-page">
<PageHeader title="智能工具" description="使用智能工具提升工作效率" />
<div class="page-body">
<section class="section">
<div class="container">
<div class="tools-grid">
<div
v-for="tool in tools"
:key="tool.id"
:class="['tool-card', { disabled: !tool.available }]"
>
<RouterLink v-if="tool.available" :to="tool.path" class="tool-link">
<div class="tool-icon">{{ tool.icon }}</div>
<h3 class="tool-name">{{ tool.name }}</h3>
<p class="tool-description">{{ tool.description }}</p>
<div class="tool-footer"><span class="tool-arrow"></span></div>
</RouterLink>
<div v-else class="tool-disabled">
<div class="tool-icon">{{ tool.icon }}</div>
<h3 class="tool-name">{{ tool.name }}</h3>
<p class="tool-description">{{ tool.description }}</p>
<span class="tool-badge">仅投保人可见</span>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</PageContainer>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useAuthStore } from '@/stores/auth'
import PageContainer from '@/components/PageContainer.vue'
import PageHeader from '@/components/PageHeader.vue'
const auth = useAuthStore()
const isPolicyholder = computed(() => auth.user?.role === '投保人')
const tools = computed(() => {
const p = isPolicyholder.value
return [
{ id: 'premium-calculator', name: '保费测算工具', description: '根据项目信息、风险等级等参数计算保费', icon: '💰', path: '/dashboard/tools/premium-calculator', available: true },
{ id: 'icf-editor', name: 'ICF智能修改', description: '智能辅助修改知情同意书ICF内容', icon: '📝', path: '/dashboard/tools/icf-editor', available: p },
{ id: 'risk-scoring', name: '方案风险评分', description: '对试验方案进行风险评分', icon: '📊', path: '/dashboard/tools/risk-scoring', available: p },
{ id: 'protocol-risk', name: '方案风险评估', description: '借助 AI 评估方案信息不足风险与偏倚风险', icon: '🔬', path: '/dashboard/tools/protocol-risk', available: true },
{ id: 'drug-safety', name: '药安查', description: '药物安全数据查询,不良反应与警戒信息检索', icon: '🔍', path: '/dashboard/tools/drug-safety', available: true }
]
})
</script>
<style scoped>
@import '@/pages/dashboard/Tools.css';
</style>

View File

@ -12,15 +12,20 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Vue */
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -1,12 +1,17 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
port: 3000,
open: true
}
})