260 lines
11 KiB
TypeScript
260 lines
11 KiB
TypeScript
import { useState } from 'react'
|
||
import PageContainer from '../../components/PageContainer'
|
||
import PageHeader from '../../components/PageHeader'
|
||
import './ProjectQuotes.css'
|
||
|
||
const ALL_INSURERS = [
|
||
{ id: 'pacific', name: '太平洋' },
|
||
{ id: 'taiping', name: '太平' },
|
||
{ id: 'dadi', name: '大地' },
|
||
{ id: 'pingan', name: '平安' },
|
||
{ id: 'huatai', name: '华泰财' },
|
||
{ id: 'yatai', name: '亚太' },
|
||
]
|
||
|
||
type QuoteItem = {
|
||
insurer: string
|
||
premium?: string
|
||
coverage?: string
|
||
note?: string
|
||
status: 'pending' | 'received'
|
||
}
|
||
|
||
type FormData = {
|
||
projectCode: string
|
||
projectTitle: string
|
||
sponsor: string
|
||
projectPhase: string
|
||
}
|
||
|
||
const initialForm: FormData = {
|
||
projectCode: '',
|
||
projectTitle: '',
|
||
sponsor: '',
|
||
projectPhase: '',
|
||
}
|
||
|
||
function ProjectQuotes() {
|
||
const [fillMode, setFillMode] = useState<'manual' | 'upload'>('manual')
|
||
const [formData, setFormData] = useState<FormData>(initialForm)
|
||
const [uploading, setUploading] = useState(false)
|
||
const [aiQuote, setAiQuote] = useState<string | null>(null)
|
||
const [generatingQuote, setGeneratingQuote] = useState(false)
|
||
const [preciseSent, setPreciseSent] = useState(false)
|
||
const [sendingPrecise, setSendingPrecise] = useState(false)
|
||
const [quotes, setQuotes] = useState<QuoteItem[]>([])
|
||
const [preciseReceived, setPreciseReceived] = useState(false)
|
||
|
||
const canGenerateQuote =
|
||
formData.projectCode.trim() &&
|
||
formData.projectTitle.trim() &&
|
||
formData.sponsor.trim() &&
|
||
formData.projectPhase.trim()
|
||
|
||
const handleUploadChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const file = e.target.files?.[0]
|
||
if (!file) return
|
||
setUploading(true)
|
||
await new Promise((r) => setTimeout(r, 1200))
|
||
setFormData({
|
||
projectCode: 'CT-2025-' + Math.floor(1000 + Math.random() * 9000),
|
||
projectTitle: file.name.replace(/\.[^.]+$/, '') || '临床试验方案',
|
||
sponsor: '示例申办者',
|
||
projectPhase: 'I期',
|
||
})
|
||
setUploading(false)
|
||
}
|
||
|
||
const handleGenerateQuote = async () => {
|
||
if (!canGenerateQuote) return
|
||
setGeneratingQuote(true)
|
||
await new Promise((r) => setTimeout(r, 1500))
|
||
setAiQuote(
|
||
`基于当前项目信息(${formData.projectTitle},${formData.projectPhase})的预估报价:\n` +
|
||
'· 建议每人保额:80–120 万\n· 每次事故限额:400–600 万\n· 预估年保费区间:约 1.1 万–1.4 万元\n(实际以各保司精准报价为准)'
|
||
)
|
||
setGeneratingQuote(false)
|
||
}
|
||
|
||
const handleGetPreciseQuote = async () => {
|
||
if (!aiQuote) return
|
||
setSendingPrecise(true)
|
||
setPreciseSent(true)
|
||
setQuotes(ALL_INSURERS.map(({ name }) => ({ insurer: name, status: 'pending' as const })))
|
||
setSendingPrecise(false)
|
||
// Mock: 保司回复后展示
|
||
const mockQuotes: QuoteItem[] = [
|
||
{ insurer: '太平洋', premium: '¥12,800/年', coverage: '每人保额 100 万,每次事故 500 万', note: '3 个工作日内出单', status: 'received' },
|
||
{ insurer: '太平', premium: '¥11,500/年', coverage: '每人保额 80 万,每次事故 400 万', note: '支持医疗直付', status: 'received' },
|
||
{ insurer: '大地', premium: '¥13,200/年', coverage: '每人保额 100 万,每次事故 600 万', note: '含 SUSAR/SAE 专项', status: 'received' },
|
||
{ insurer: '平安', premium: '¥14,000/年', coverage: '每人保额 120 万,每次事故 800 万', note: '全国网点理赔', status: 'received' },
|
||
{ insurer: '华泰财', premium: '¥12,000/年', coverage: '每人保额 100 万,每次事故 500 万', note: '与经纪服务联动', status: 'received' },
|
||
{ insurer: '亚太', premium: '¥11,800/年', coverage: '每人保额 80 万,每次事故 400 万', note: '5 个工作日报价', status: 'received' },
|
||
]
|
||
setTimeout(() => {
|
||
setQuotes(mockQuotes)
|
||
setPreciseReceived(true)
|
||
}, 2000)
|
||
}
|
||
|
||
const startNewInquiry = () => {
|
||
setFormData(initialForm)
|
||
setAiQuote(null)
|
||
setPreciseSent(false)
|
||
setQuotes([])
|
||
setPreciseReceived(false)
|
||
}
|
||
|
||
return (
|
||
<PageContainer>
|
||
<div className="project-quotes-page">
|
||
<PageHeader
|
||
title="报价页面"
|
||
description="填写项目方案编号、项目标题、申办者、项目分期(可手动填写或上传方案由 AI 识别),生成报价后向各保司获取精准报价;系统将询价发至保司,保司回复至 rmo@vdano.com,经临研安审核后在此展示。"
|
||
/>
|
||
<div className="page-body">
|
||
<section className="section section-project-quotes">
|
||
<div className="container">
|
||
<div className="quote-form-card card main-card">
|
||
<h3 className="quote-form-title">1. 报价需提交的资料</h3>
|
||
<div className="quote-fill-mode">
|
||
<label className="quote-radio">
|
||
<input
|
||
type="radio"
|
||
name="fillMode"
|
||
checked={fillMode === 'manual'}
|
||
onChange={() => setFillMode('manual')}
|
||
/>
|
||
<span>手动填写</span>
|
||
</label>
|
||
<label className="quote-radio">
|
||
<input
|
||
type="radio"
|
||
name="fillMode"
|
||
checked={fillMode === 'upload'}
|
||
onChange={() => setFillMode('upload')}
|
||
/>
|
||
<span>上传项目方案(AI 识别填充)</span>
|
||
</label>
|
||
</div>
|
||
|
||
{fillMode === 'upload' && (
|
||
<div className="form-group">
|
||
<label>上传项目方案</label>
|
||
<input type="file" accept=".pdf,.doc,.docx" onChange={handleUploadChange} />
|
||
{uploading && <p className="form-hint">AI 识别中…</p>}
|
||
</div>
|
||
)}
|
||
|
||
<div className="quote-form-grid">
|
||
<div className="form-group">
|
||
<label>项目方案编号</label>
|
||
<input
|
||
type="text"
|
||
value={formData.projectCode}
|
||
onChange={(e) => setFormData((d) => ({ ...d, projectCode: e.target.value }))}
|
||
placeHolder="如:CT-2025-001"
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label>项目标题</label>
|
||
<input
|
||
type="text"
|
||
value={formData.projectTitle}
|
||
onChange={(e) => setFormData((d) => ({ ...d, projectTitle: e.target.value }))}
|
||
placeHolder="试验方案标题"
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label>申办者</label>
|
||
<input
|
||
type="text"
|
||
value={formData.sponsor}
|
||
onChange={(e) => setFormData((d) => ({ ...d, sponsor: e.target.value }))}
|
||
placeHolder="申办者名称"
|
||
/>
|
||
</div>
|
||
<div className="form-group">
|
||
<label>项目分期</label>
|
||
<input
|
||
type="text"
|
||
value={formData.projectPhase}
|
||
onChange={(e) => setFormData((d) => ({ ...d, projectPhase: e.target.value }))}
|
||
placeHolder="如:I期、II期、III期"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="quote-form-card card main-card">
|
||
<h3 className="quote-form-title">2. 生成报价</h3>
|
||
<p className="quote-form-desc">由 AI 基于上述资料自动生成预估报价。</p>
|
||
<div className="form-actions">
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary"
|
||
disabled={!canGenerateQuote || generatingQuote}
|
||
onClick={handleGenerateQuote}
|
||
>
|
||
{generatingQuote ? 'AI 生成中…' : '生成报价'}
|
||
</button>
|
||
</div>
|
||
{aiQuote && (
|
||
<div className="quote-ai-result">
|
||
<pre>{aiQuote}</pre>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="quote-form-card card main-card">
|
||
<h3 className="quote-form-title">3. 获取精准报价</h3>
|
||
<p className="quote-form-desc">系统将报价资料整合后以 Email 发送至各保司,保司回复至 rmo@vdano.com,经临研安审核后在此展示。</p>
|
||
<div className="form-actions">
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary"
|
||
disabled={!aiQuote || sendingPrecise}
|
||
onClick={handleGetPreciseQuote}
|
||
>
|
||
{sendingPrecise ? '发送中…' : preciseSent ? '已发送至各保司' : '获取精准报价'}
|
||
</button>
|
||
{(preciseSent || preciseReceived) && (
|
||
<button type="button" className="btn btn-secondary" onClick={startNewInquiry}>
|
||
发起新询价
|
||
</button>
|
||
)}
|
||
</div>
|
||
{preciseSent && (
|
||
<p className="quote-sent-tip">
|
||
已向各保司发送询价邮件,保司将回复至 rmo@vdano.com,审核通过后展示如下。
|
||
</p>
|
||
)}
|
||
{quotes.length > 0 && (
|
||
<div className="insurer-quotes-grid">
|
||
{quotes.map((q) => (
|
||
<div key={q.insurer} className={`insurer-quote-card ${q.status === 'received' ? 'received' : 'pending'}`}>
|
||
<h4 className="insurer-quote-name">{q.insurer}</h4>
|
||
{q.status === 'pending' ? (
|
||
<p className="insurer-quote-pending">等待保司报价回复…</p>
|
||
) : (
|
||
<>
|
||
{q.premium && <p className="insurer-quote-premium">{q.premium}</p>}
|
||
{q.coverage && <p className="insurer-quote-coverage">{q.coverage}</p>}
|
||
{q.note && <p className="insurer-quote-note">{q.note}</p>}
|
||
</>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</PageContainer>
|
||
)
|
||
}
|
||
|
||
export default ProjectQuotes
|