feat(ui/ux): 优化用户体验和可访问性
- 字体加载优化: 添加 font-display: block 策略,创建 useFontLoading hook - 色彩对比度: 调整 text-muted 和 text-tertiary 颜色值确保 WCAG AA 合规 - 滚动进度条: 新增 ScrollProgress 组件,支持 reduced motion - 表单自动保存: 新增 useFormAutosave hook,防止用户数据丢失 - 返回顶部按钮: 新增 BackToTop 组件,提升长页面导航体验 - 图片懒加载: 优化 OptimizedImage 组件,添加 blur placeholder 和加载动画 所有新组件均包含完整测试,1450+ 测试通过
This commit is contained in:
@@ -9,7 +9,8 @@ import { Toast } from '@/components/ui/toast';
|
||||
import { sanitizeInput } from '@/lib/sanitize';
|
||||
import { generateCSRFToken, setCSRFTokenToStorage, getCSRFTokenFromStorage } from '@/lib/csrf';
|
||||
import { generateCaptcha } from '@/lib/security/captcha';
|
||||
import { Mail, MapPin, Send, Loader2, Clock, HeadphonesIcon, CheckCircle2, RefreshCw } from 'lucide-react';
|
||||
import { useFormAutosave } from '@/hooks/use-form-autosave';
|
||||
import { Mail, MapPin, Send, Loader2, Clock, HeadphonesIcon, CheckCircle2, RefreshCw, Save } from 'lucide-react';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
|
||||
const contactFormSchema = z.object({
|
||||
@@ -36,17 +37,29 @@ export function ContactSection() {
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
const [toastType, setToastType] = useState<'success' | 'error'>('success');
|
||||
const [formData, setFormData] = useState<ContactFormData>({
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
message: '',
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [captcha, setCaptcha] = useState(generateCaptcha('simple'));
|
||||
const [captchaAnswer, setCaptchaAnswer] = useState('');
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
// 使用表单自动保存功能
|
||||
const {
|
||||
data: formData,
|
||||
updateData,
|
||||
lastSaved,
|
||||
isRestored,
|
||||
clearSavedData,
|
||||
} = useFormAutosave<ContactFormData>({
|
||||
key: 'contact_form',
|
||||
initialData: {
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
message: '',
|
||||
},
|
||||
debounceMs: 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
@@ -83,7 +96,7 @@ export function ContactSection() {
|
||||
|
||||
const handleChange = (field: keyof ContactFormData, value: string) => {
|
||||
const sanitizedValue = sanitizeInput(value);
|
||||
setFormData((prev) => ({ ...prev, [field]: sanitizedValue }));
|
||||
updateData({ [field]: sanitizedValue });
|
||||
if (errors[field]) {
|
||||
validateField(field, sanitizedValue);
|
||||
}
|
||||
@@ -163,6 +176,7 @@ export function ContactSection() {
|
||||
|
||||
setIsSubmitting(false);
|
||||
setIsSubmitted(true);
|
||||
clearSavedData(); // 提交成功后清除保存的数据
|
||||
setToastMessage('表单提交成功!我们会尽快与您联系。');
|
||||
setToastType('success');
|
||||
setShowToast(true);
|
||||
@@ -277,7 +291,7 @@ export function ContactSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<div
|
||||
className={`
|
||||
lg:col-span-3 flex flex-col
|
||||
opacity-0 translate-y-4
|
||||
@@ -285,7 +299,34 @@ export function ContactSection() {
|
||||
`}
|
||||
>
|
||||
<div className="bg-[#F5F7FA] p-6 sm:p-8 rounded-lg border border-[#E2E8F0] flex-1 flex flex-col">
|
||||
<h3 className="text-lg font-semibold text-[#1A1A2E] mb-6">发送消息</h3>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-[#1A1A2E]">发送消息</h3>
|
||||
{/* 自动保存状态指示器 */}
|
||||
<div className="flex items-center gap-2 text-sm text-[#595959]">
|
||||
{lastSaved && (
|
||||
<>
|
||||
<Save className="w-4 h-4" />
|
||||
<span>已保存 {lastSaved.toLocaleTimeString()}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据恢复提示 */}
|
||||
{isRestored && (
|
||||
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg flex items-center justify-between">
|
||||
<span className="text-sm text-blue-700">
|
||||
已恢复您上次未提交的表单内容
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearSavedData}
|
||||
className="text-sm text-blue-600 hover:text-blue-800 underline"
|
||||
>
|
||||
清除
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isSubmitted ? (
|
||||
<div className="text-center py-12 flex-1 flex items-center justify-center" data-testid="success-message">
|
||||
|
||||
Reference in New Issue
Block a user