110 lines
5.4 KiB
TypeScript
110 lines
5.4 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { useCMSTexts } from '@/services/cms'
|
|
|
|
type Q = { id:string, group_key:string, group_name:string, type:string, title:string, options:any[], sort_order:number }
|
|
const STORAGE_KEY='diagnosis_progress'
|
|
|
|
export default function Diagnosis(){
|
|
const { data: texts } = useCMSTexts()
|
|
const [questions,setQuestions]=useState<Q[]>([])
|
|
const [answers,setAnswers]=useState<Record<string,any>>({})
|
|
const [idx,setIdx]=useState(0)
|
|
const nav=useNavigate()
|
|
const [loading,setLoading]=useState(true)
|
|
|
|
useEffect(()=>{
|
|
fetch('/api/diagnosis/questions').then(r=>r.json()).then(j=>{
|
|
const list = (j.data||[]).sort((a:Q,b:Q)=> b.sort_order - a.sort_order)
|
|
setQuestions(list)
|
|
// 断点续填
|
|
const saved = localStorage.getItem(STORAGE_KEY)
|
|
if(saved){
|
|
try{ const p=JSON.parse(saved); if(p.answers) setAnswers(p.answers); if(typeof p.idx==='number') setIdx(p.idx)}catch{}
|
|
}
|
|
setLoading(false)
|
|
})
|
|
},[])
|
|
useEffect(()=>{
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({answers, idx}))
|
|
},[answers, idx])
|
|
|
|
const q = questions[idx]
|
|
const progress = questions.length? Math.round(((idx)/questions.length)*100):0
|
|
const answeredCount = Object.keys(answers).length
|
|
|
|
const handleAnswer=(val:any)=>{
|
|
const next={...answers, [q.id]:val}
|
|
setAnswers(next)
|
|
}
|
|
const nextQ=()=>{
|
|
if(idx<questions.length-1) setIdx(idx+1)
|
|
else submit()
|
|
}
|
|
const prevQ=()=> setIdx(Math.max(0, idx-1))
|
|
|
|
const submit=async()=>{
|
|
// 创建会话并提交
|
|
const sess = await fetch('/api/diagnosis/sessions',{method:'POST'}).then(r=>r.json())
|
|
const token = sess.data.token
|
|
await fetch(`/api/diagnosis/sessions/${token}`,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({answers})})
|
|
const rep = await fetch(`/api/diagnosis/sessions/${token}/submit`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({answers})}).then(r=>r.json())
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
nav(`/diagnosis/report/${rep.data.token}`)
|
|
}
|
|
|
|
if(loading) return <div className="container section">加载问卷... <2s</div>
|
|
if(!q) return <div className="container section">暂无可用题目,请稍后再试。</div>
|
|
|
|
const isAnswered = answers[q.id]!==undefined
|
|
|
|
return (
|
|
<div>
|
|
<div className="page-header">
|
|
<div className="container">
|
|
<h1>企业进化诊断</h1>
|
|
<p>15-20题 · 按四大价值分组 · 区间选择无需精确填写 · 免责:初步分析仅供参考</p>
|
|
<div style={{marginTop:12, background:'rgba(255,255,255,0.2)', height:6, borderRadius:6}}>
|
|
<div style={{width:`${progress}%`, height:'100%', background:'#fff', borderRadius:6}} />
|
|
</div>
|
|
<div style={{fontSize:12, color:'rgba(255,255,255,0.8)', marginTop:4}}>{idx+1}/{questions.length} · 已答 {answeredCount} 题 · 本地自动保存,刷新不丢失</div>
|
|
</div>
|
|
</div>
|
|
<section className="section">
|
|
<div className="container" style={{maxWidth:720}}>
|
|
<div className="card">
|
|
<div style={{fontSize:12, color:'#0f6b5b', fontWeight:800}}>{q.group_name} · {q.type==='scale'?'量表 1-5':q.type==='multiple'?'多选':'单选'}</div>
|
|
<h3 style={{marginTop:6}}>{idx+1}. {q.title}</h3>
|
|
<div style={{marginTop:12, display:'grid', gap:8}}>
|
|
{q.options.map((opt:any)=> {
|
|
const selected = q.type==='multiple' ? (Array.isArray(answers[q.id]) && answers[q.id].includes(opt.label)) : answers[q.id]===opt.label
|
|
return (
|
|
<label key={opt.label} style={{border: selected?'2px solid #163E70':'1px solid #e2e8f0', background: selected?'#eff6ff':'#fff', padding:'10px 12px', borderRadius:8, cursor:'pointer', display:'flex', justifyContent:'space-between'}}>
|
|
<span><input type={q.type==='multiple'?'checkbox':'radio'} checked={!!selected} onChange={()=>{
|
|
if(q.type==='multiple'){
|
|
const arr=Array.isArray(answers[q.id])? [...answers[q.id]]:[]
|
|
if(selected) handleAnswer(arr.filter(v=>v!==opt.label))
|
|
else handleAnswer([...arr, opt.label])
|
|
} else handleAnswer(opt.label)
|
|
}} style={{marginRight:8}} />{opt.label}</span>
|
|
<span style={{fontSize:11,color:'#64748b'}}>{opt.score}分</span>
|
|
</label>
|
|
)
|
|
})}
|
|
</div>
|
|
<div style={{display:'flex', gap:10, marginTop:16}}>
|
|
<button className="btn btn-ghost" onClick={prevQ} disabled={idx===0}>上一步</button>
|
|
<button className="btn btn-primary" style={{flex:1}} onClick={nextQ} disabled={!isAnswered}>{idx===questions.length-1?'提交生成报告':'下一步'}</button>
|
|
</div>
|
|
<div style={{fontSize:11, color:'#94a3b8', marginTop:8}}>{texts['diagnosis.input_note'] || '采用区间选择,不收集敏感财务数据。'}</div>
|
|
</div>
|
|
<div style={{marginTop:12, display:'flex', gap:10}}>
|
|
<Link to="/self-check" className="btn btn-ghost">返回轻量对照</Link>
|
|
<button className="btn btn-ghost" onClick={()=>{localStorage.removeItem(STORAGE_KEY); setAnswers({}); setIdx(0)}}>重置进度</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
)
|
|
}
|