NotificationContext.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
  2. import Toast, { ToastType } from '../components/Toast';
  3. import ConfirmDialog from '../components/ConfirmDialog';
  4. // Toast 配置
  5. interface ToastConfig {
  6. message: string;
  7. type: ToastType;
  8. }
  9. // Confirm 配置
  10. interface ConfirmConfig {
  11. title?: string;
  12. message: string;
  13. confirmText?: string;
  14. cancelText?: string;
  15. danger?: boolean;
  16. }
  17. // Context 类型
  18. interface NotificationContextType {
  19. showToast: (message: string, type: ToastType) => void;
  20. showConfirm: (config: ConfirmConfig) => Promise<boolean>;
  21. }
  22. const NotificationContext = createContext<NotificationContextType | undefined>(undefined);
  23. // Provider Props
  24. interface NotificationProviderProps {
  25. children: ReactNode;
  26. }
  27. export const NotificationProvider: React.FC<NotificationProviderProps> = ({ children }) => {
  28. // Toast 状态
  29. const [toast, setToast] = useState<ToastConfig | null>(null);
  30. // Confirm 状态
  31. const [confirmConfig, setConfirmConfig] = useState<ConfirmConfig | null>(null);
  32. const [confirmResolver, setConfirmResolver] = useState<((value: boolean) => void) | null>(null);
  33. const normalizeToastMessage = useCallback((message: string): string => {
  34. // 去掉服务端可能携带的状态码前缀
  35. let normalized = message.replace(/^\s*\d{3}\s*:\s*/, '').trim();
  36. return normalized || message;
  37. }, []);
  38. // 显示 Toast
  39. const showToast = useCallback((message: string, type: ToastType) => {
  40. setToast({ message: normalizeToastMessage(message), type });
  41. }, [normalizeToastMessage]);
  42. // 显示 Confirm
  43. const showConfirm = useCallback((config: ConfirmConfig): Promise<boolean> => {
  44. return new Promise((resolve) => {
  45. setConfirmConfig(config);
  46. setConfirmResolver(() => resolve);
  47. });
  48. }, []);
  49. // 处理确认
  50. const handleConfirm = useCallback(() => {
  51. if (confirmResolver) {
  52. confirmResolver(true);
  53. setConfirmResolver(null);
  54. }
  55. setConfirmConfig(null);
  56. }, [confirmResolver]);
  57. // 处理取消
  58. const handleCancel = useCallback(() => {
  59. if (confirmResolver) {
  60. confirmResolver(false);
  61. setConfirmResolver(null);
  62. }
  63. setConfirmConfig(null);
  64. }, [confirmResolver]);
  65. return (
  66. <NotificationContext.Provider value={{ showToast, showConfirm }}>
  67. {children}
  68. {/* Toast 组件 */}
  69. <Toast
  70. message={toast?.message || ''}
  71. type={toast?.type || 'success'}
  72. isVisible={!!toast}
  73. onClose={() => setToast(null)}
  74. />
  75. {/* Confirm 对话框 */}
  76. <ConfirmDialog
  77. isOpen={!!confirmConfig}
  78. title={confirmConfig?.title}
  79. message={confirmConfig?.message || ''}
  80. confirmText={confirmConfig?.confirmText}
  81. cancelText={confirmConfig?.cancelText}
  82. danger={confirmConfig?.danger}
  83. onConfirm={handleConfirm}
  84. onCancel={handleCancel}
  85. />
  86. </NotificationContext.Provider>
  87. );
  88. };
  89. // useToast Hook
  90. export const useToast = () => {
  91. const context = useContext(NotificationContext);
  92. if (!context) {
  93. throw new Error('useToast must be used within NotificationProvider');
  94. }
  95. return { showToast: context.showToast };
  96. };
  97. // useConfirm Hook
  98. export const useConfirm = () => {
  99. const context = useContext(NotificationContext);
  100. if (!context) {
  101. throw new Error('useConfirm must be used within NotificationProvider');
  102. }
  103. return { showConfirm: context.showConfirm };
  104. };