ExitModal.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import React from 'react';
  2. import { Modal, Button, Space, Typography, message } from 'antd';
  3. import {
  4. PoweroffOutlined,
  5. LogoutOutlined,
  6. CloseOutlined,
  7. } from '@ant-design/icons';
  8. import { useDispatch } from 'react-redux';
  9. import { clearUserInfo } from '../states/user_info';
  10. const { Text } = Typography;
  11. interface ExitModalProps {
  12. visible: boolean;
  13. onClose: () => void;
  14. }
  15. declare global {
  16. interface Window {
  17. electronAPI: {
  18. exitApp: () => Promise<{ success: boolean }>;
  19. shutdownSystem: () => Promise<{
  20. success: boolean;
  21. error?: string;
  22. requiresAdmin?: boolean;
  23. }>;
  24. checkCameraPermission?: () => Promise<boolean>;
  25. requestCameraPermission?: () => Promise<boolean>;
  26. };
  27. }
  28. }
  29. const ExitModal: React.FC<ExitModalProps> = ({ visible, onClose }) => {
  30. const dispatch = useDispatch();
  31. const handleExit = async (type: 'close' | 'logout' | 'shutdown') => {
  32. // 对危险操作进行二次确认
  33. const isDangerousOperation = type === 'shutdown' || type === 'logout';
  34. if (isDangerousOperation) {
  35. const confirmMessages = {
  36. shutdown: {
  37. title: '确认关机',
  38. content: '确定要关闭系统吗?此操作将关闭计算机。',
  39. },
  40. logout: {
  41. title: '确认注销',
  42. content: '确定要注销当前用户吗?您将退出登录状态。',
  43. },
  44. };
  45. const config = confirmMessages[type];
  46. Modal.confirm({
  47. title: config.title,
  48. content: config.content,
  49. okText: '确认',
  50. cancelText: '取消',
  51. centered: true,
  52. onOk: async () => {
  53. await executeAction(type);
  54. },
  55. });
  56. return;
  57. }
  58. // 非危险操作直接执行
  59. await executeAction(type);
  60. };
  61. const executeAction = async (type: 'close' | 'logout' | 'shutdown') => {
  62. try {
  63. let result;
  64. let actionName = '';
  65. switch (type) {
  66. case 'close':
  67. actionName = '关闭程序';
  68. result = await window.electronAPI.exitApp();
  69. break;
  70. case 'logout':
  71. actionName = '注销用户';
  72. // 应用级注销:清除 Redux 用户状态
  73. dispatch(clearUserInfo());
  74. message.success('已退出登录');
  75. onClose();
  76. return; // 直接返回,不需要后续处理
  77. case 'shutdown':
  78. actionName = '关机';
  79. result = await window.electronAPI.shutdownSystem();
  80. break;
  81. default:
  82. return;
  83. }
  84. if (result && result.success) {
  85. message.success(`${actionName}操作已执行`);
  86. if (type === 'close') {
  87. // 关闭程序操作会直接退出,不需要关闭弹框
  88. return;
  89. }
  90. } else if (result) {
  91. // 处理不同类型的错误
  92. if (result.requiresAdmin) {
  93. message.warning({
  94. content: `${actionName}需要管理员权限,请以管理员身份运行程序后重试`,
  95. duration: 5,
  96. });
  97. } else {
  98. message.error({
  99. content: `${actionName}失败: ${result.error || '未知错误'}`,
  100. duration: 4,
  101. });
  102. }
  103. }
  104. } catch (error) {
  105. console.error('系统操作失败:', error);
  106. message.error({
  107. content: '操作失败,请检查网络连接后重试',
  108. duration: 3,
  109. });
  110. }
  111. onClose();
  112. };
  113. return (
  114. <Modal
  115. data-testid="exit-modal"
  116. title="选择退出方式"
  117. open={visible}
  118. onCancel={onClose}
  119. footer={null}
  120. centered
  121. width={400}
  122. >
  123. <div style={{ textAlign: 'center', padding: '20px 0' }}>
  124. <Text type="secondary" style={{ marginBottom: 24, display: 'block' }}>
  125. 请选择您希望执行的操作
  126. </Text>
  127. <Space direction="vertical" size="large" style={{ width: '100%' }}>
  128. <Button
  129. data-testid="exit-modal-close-button"
  130. type="default"
  131. size="large"
  132. icon={<CloseOutlined />}
  133. onClick={() => handleExit('close')}
  134. style={{
  135. width: '100%',
  136. height: 48,
  137. display: 'flex',
  138. alignItems: 'center',
  139. justifyContent: 'center',
  140. }}
  141. >
  142. 关闭程序
  143. </Button>
  144. <Button
  145. data-testid="exit-modal-logout-button"
  146. type="default"
  147. size="large"
  148. icon={<LogoutOutlined />}
  149. onClick={() => handleExit('logout')}
  150. style={{
  151. width: '100%',
  152. height: 48,
  153. display: 'flex',
  154. alignItems: 'center',
  155. justifyContent: 'center',
  156. }}
  157. >
  158. 注销用户
  159. </Button>
  160. <Button
  161. data-testid="exit-modal-shutdown-button"
  162. type="default"
  163. size="large"
  164. icon={<PoweroffOutlined />}
  165. onClick={() => handleExit('shutdown')}
  166. danger
  167. style={{
  168. width: '100%',
  169. height: 48,
  170. display: 'flex',
  171. alignItems: 'center',
  172. justifyContent: 'center',
  173. }}
  174. >
  175. 关机
  176. </Button>
  177. <Button
  178. data-testid="exit-modal-cancel-button"
  179. type="default"
  180. size="large"
  181. onClick={onClose}
  182. style={{
  183. width: '100%',
  184. height: 48,
  185. marginTop: 16,
  186. }}
  187. >
  188. 取消
  189. </Button>
  190. </Space>
  191. </div>
  192. </Modal>
  193. );
  194. };
  195. export default ExitModal;