feat: 问卷支持暂存功能,打开时自动恢复上次填写内容
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { Star, ChevronDown, ChevronRight, Check, Search, ClipboardList, Send } from 'lucide-react'
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react'
|
||||
import { Star, ChevronDown, ChevronRight, Check, Search, ClipboardList, Send, Save, RotateCcw } from 'lucide-react'
|
||||
import Modal from './ui/Modal'
|
||||
import { toast } from 'sonner'
|
||||
import api from '../lib/api'
|
||||
@@ -11,11 +11,56 @@ interface FeatureScore {
|
||||
remark: string
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'survey-draft'
|
||||
|
||||
export default function SurveyModal({ open, onClose }: { open: boolean; onClose: () => void }) {
|
||||
const [expandedPage, setExpandedPage] = useState<number | null>(1)
|
||||
const [scores, setScores] = useState<Record<string, FeatureScore>>({})
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [savedAt, setSavedAt] = useState<string | null>(null)
|
||||
|
||||
/** 从 localStorage 恢复暂存数据 */
|
||||
const loadDraft = useCallback(() => {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) {
|
||||
const data = JSON.parse(raw)
|
||||
if (data.scores && typeof data.scores === 'object') {
|
||||
setScores(data.scores)
|
||||
setSavedAt(data.savedAt || null)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 暂存到 localStorage */
|
||||
const saveDraft = useCallback(() => {
|
||||
const hasData = Object.values(scores).some(s => s.score > 0 || s.useful || s.remark)
|
||||
if (!hasData) {
|
||||
toast.info('暂无可保存的填写内容')
|
||||
return
|
||||
}
|
||||
const now = new Date().toISOString()
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ scores, savedAt: now }))
|
||||
setSavedAt(now)
|
||||
toast.success('已暂存填写内容,下次打开可恢复')
|
||||
}, [scores])
|
||||
|
||||
/** 清空暂存 */
|
||||
const clearDraft = useCallback(() => {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
setScores({})
|
||||
setSavedAt(null)
|
||||
toast.info('已清空所有填写内容')
|
||||
}, [])
|
||||
|
||||
/** 打开时恢复暂存 */
|
||||
useEffect(() => {
|
||||
if (open) loadDraft()
|
||||
}, [open, loadDraft])
|
||||
|
||||
const setScore = (id: string, score: number) => {
|
||||
setScores(prev => ({
|
||||
@@ -61,10 +106,14 @@ export default function SurveyModal({ open, onClose }: { open: boolean; onClose:
|
||||
try {
|
||||
await api.post('/survey/submit', { items })
|
||||
toast.success(`已提交 ${items.length} 项评分,感谢您的反馈!`)
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
setSavedAt(null)
|
||||
onClose()
|
||||
} catch {
|
||||
localStorage.setItem('survey-results', JSON.stringify({ items, submittedAt: new Date().toISOString() }))
|
||||
toast.success(`已保存 ${items.length} 项评分到本地`)
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
setSavedAt(null)
|
||||
onClose()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
@@ -168,13 +217,26 @@ export default function SurveyModal({ open, onClose }: { open: boolean; onClose:
|
||||
|
||||
{/* 底部提交 */}
|
||||
<div className="border-t border-gray-200 px-4 py-3 flex items-center justify-between">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-xs text-gray-500">已评分 {ratedCount} / {totalFeatures} 项</span>
|
||||
{savedAt && <span className="text-xs text-gray-400">上次暂存:{new Date(savedAt).toLocaleString('zh-CN')}</span>}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
onClick={clearDraft}
|
||||
disabled={ratedCount === 0 && !savedAt}
|
||||
className="px-3 py-1.5 text-sm text-gray-500 border border-gray-200 rounded-md hover:bg-gray-50 disabled:opacity-50 flex items-center gap-1"
|
||||
>
|
||||
关闭
|
||||
<RotateCcw className="w-3.5 h-3.5" />
|
||||
清空
|
||||
</button>
|
||||
<button
|
||||
onClick={saveDraft}
|
||||
disabled={ratedCount === 0}
|
||||
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-200 rounded-md hover:bg-gray-50 disabled:opacity-50 flex items-center gap-1"
|
||||
>
|
||||
<Save className="w-3.5 h-3.5" />
|
||||
暂存
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
|
||||
Reference in New Issue
Block a user