From 7716de63f1dec01f67147d4a30102a6f45196e90 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Tue, 28 Jul 2026 14:49:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=97=AE=E5=8D=B7=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9A=82=E5=AD=98=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=89=93=E5=BC=80?= =?UTF-8?q?=E6=97=B6=E8=87=AA=E5=8A=A8=E6=81=A2=E5=A4=8D=E4=B8=8A=E6=AC=A1?= =?UTF-8?q?=E5=A1=AB=E5=86=99=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/SurveyModal.tsx | 74 +++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/SurveyModal.tsx b/frontend/src/components/SurveyModal.tsx index d3b5c95..cae9df2 100644 --- a/frontend/src/components/SurveyModal.tsx +++ b/frontend/src/components/SurveyModal.tsx @@ -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(1) const [scores, setScores] = useState>({}) const [searchQuery, setSearchQuery] = useState('') const [submitting, setSubmitting] = useState(false) + const [savedAt, setSavedAt] = useState(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: {/* 底部提交 */}
- 已评分 {ratedCount} / {totalFeatures} 项 +
+ 已评分 {ratedCount} / {totalFeatures} 项 + {savedAt && 上次暂存:{new Date(savedAt).toLocaleString('zh-CN')}} +
+