RMO-Front/src/pages/dashboard/ProjectQuotes.tsx

260 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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` +
'· 建议每人保额80120 万\n· 每次事故限额400600 万\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