Pārlūkot izejas kodu

fix (1.66.1 -> 1.66.2): 优化身份证识别模态框,添加图片方向验证和改进用户交互

- 在 handleUpload 函数中添加图片宽高比验证,拒绝竖向身份证照片
- 优化自动拍照和手动上传按钮的点击处理,添加重拍和停止摄像头逻辑
- 更新上传区域提示文本,明确只支持横向身份证照片
- 添加环境检测的调试日志输出,便于问题排查

改动文件:
- src/components/IDCardRecognitionModal.tsx
szy 18 stundas atpakaļ
vecāks
revīzija
c621498c91

+ 14 - 0
CHANGELOG.md

@@ -2,6 +2,20 @@
 
 本项目的所有重要变更都将记录在此文件中.
 
+## [1.66.2] - 2026-01-12 17:59
+
+### 修复 (Fixed)
+
+- **优化身份证识别模态框,添加图片方向验证和改进用户交互** - 在身份证识别组件中新增图片方向检查功能,只接受横向身份证照片,并优化按钮交互逻辑,提升用户体验
+  - 在 handleUpload 函数中添加图片宽高比验证,拒绝竖向身份证照片
+  - 优化自动拍照和手动上传按钮的点击处理,添加重拍和停止摄像头逻辑
+  - 更新上传区域提示文本,明确只支持横向身份证照片
+  - 添加环境检测的调试日志输出,便于问题排查
+
+**改动文件:**
+
+- src/components/IDCardRecognitionModal.tsx
+
 ## [1.66.1] - 2026-01-12 15:58
 
 ### 新增 (Added)

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.66.1",
+  "version": "1.66.2",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 433 - 0
src/components/IDCardRecognitionModal copy.tsx

@@ -0,0 +1,433 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal, Button, Space, message, Spin, Upload } from 'antd';
+import {
+  CameraOutlined,
+  CloseOutlined,
+  ReloadOutlined,
+  UploadOutlined,
+  IdcardOutlined,
+} from '@ant-design/icons';
+import { CameraServiceFactory } from '@/services/camera/CameraServiceFactory';
+import axiosInstance from '@/API/interceptor';
+
+interface IDCardRecognitionModalProps {
+  visible: boolean;
+  onClose: () => void;
+  onRecognitionSuccess: (data: IDCardData) => void;
+}
+
+interface IDCardData {
+  name?: string;
+  idNumber?: string;
+  gender?: string;
+  birthDate?: string;
+  address?: string;
+}
+
+/**
+ * 身份证识别 Modal 组件
+ * 支持自动拍照和手动上传图片两种方式
+ */
+const IDCardRecognitionModal: React.FC<IDCardRecognitionModalProps> = ({
+  visible,
+  onClose,
+  onRecognitionSuccess,
+}) => {
+  const videoRef = useRef<HTMLVideoElement>(null);
+  const streamRef = useRef<MediaStream | null>(null);
+
+  const [capturedImage, setCapturedImage] = useState<string | null>(null);
+  const [uploadedImage, setUploadedImage] = useState<string | null>(null);
+  const [isInitializing, setIsInitializing] = useState(false);
+  const [isRecognizing, setIsRecognizing] = useState(false);
+  const [mode, setMode] = useState<'camera' | 'upload'>('camera');
+
+  /**
+   * 初始化摄像头
+   */
+  useEffect(() => {
+    if (visible && mode === 'camera' && !capturedImage) {
+      initCamera();
+    }
+
+    return () => {
+      stopCamera();
+    };
+  }, [visible, mode, capturedImage]);
+
+  /**
+   * 初始化摄像头
+   */
+  const initCamera = async () => {
+    setIsInitializing(true);
+
+    try {
+      console.log('[IDCardRecognition] 初始化摄像头...');
+
+      const cameraService = CameraServiceFactory.create();
+
+      const hasPermission = await cameraService.requestPermission();
+      if (!hasPermission) {
+        throw new Error('用户拒绝了摄像头权限');
+      }
+
+      const stream = await cameraService.getMediaStream({
+        video: {
+          width: { ideal: 1280 },
+          height: { ideal: 720 },
+          facingMode: 'environment', // 使用后置摄像头
+        },
+      });
+
+      streamRef.current = stream;
+
+      if (videoRef.current) {
+        videoRef.current.onloadedmetadata = async () => {
+          try {
+            if (videoRef.current) {
+              await videoRef.current.play();
+              console.log('[IDCardRecognition] 视频开始播放');
+            }
+          } catch (playError: any) {
+            console.error('[IDCardRecognition] 视频播放失败:', playError);
+            message.error('视频播放失败: ' + playError.message);
+          }
+        };
+
+        videoRef.current.srcObject = stream;
+      }
+
+      console.log('[IDCardRecognition] 摄像头初始化成功');
+    } catch (err: any) {
+      console.error('[IDCardRecognition] 摄像头初始化失败:', err);
+      message.error(err.message || '摄像头初始化失败');
+    } finally {
+      setIsInitializing(false);
+    }
+  };
+
+  /**
+   * 停止摄像头
+   */
+  const stopCamera = () => {
+    if (streamRef.current) {
+      console.log('[IDCardRecognition] 停止摄像头');
+      streamRef.current.getTracks().forEach((track) => track.stop());
+      streamRef.current = null;
+    }
+  };
+
+  /**
+   * 拍照
+   */
+  const handleCapture = async () => {
+    if (!streamRef.current) {
+      message.error('摄像头未就绪');
+      return;
+    }
+
+    try {
+      console.log('[IDCardRecognition] 开始拍照...');
+
+      const cameraService = CameraServiceFactory.create();
+      const base64Image = await cameraService.capturePhoto(streamRef.current);
+
+      console.log(
+        '[IDCardRecognition] 拍照成功,图片大小:',
+        base64Image.length
+      );
+
+      setCapturedImage(base64Image);
+      stopCamera();
+
+      message.success('拍照成功');
+    } catch (err: any) {
+      console.error('[IDCardRecognition] 拍照失败:', err);
+      message.error(err.message || '拍照失败');
+    }
+  };
+
+  /**
+   * 重拍
+   */
+  const handleRetake = () => {
+    console.log('[IDCardRecognition] 重拍');
+    setCapturedImage(null);
+  };
+
+  /**
+   * 上传文件处理
+   */
+  const handleUpload = (file: File) => {
+    const reader = new FileReader();
+    reader.onload = (e) => {
+      const result = e.target?.result as string;
+      setUploadedImage(result);
+      setCapturedImage(null);
+      message.success('图片上传成功');
+    };
+    reader.readAsDataURL(file);
+    return false; // 阻止自动上传
+  };
+
+  /**
+   * 识别身份证
+   */
+  const handleRecognize = async () => {
+    const imageData = capturedImage || uploadedImage;
+    if (!imageData) {
+      message.error('请先拍照或上传身份证图片');
+      return;
+    }
+
+    try {
+      setIsRecognizing(true);
+
+      console.log('[IDCardRecognition] 开始识别身份证...');
+
+      // 上传图片到后端进行识别
+      const response = await axiosInstance.post('/auth/task/ocr/id_card', {
+        type: 'png',
+        data: imageData.replace(/^data:image\/[a-z]+;base64,/, ''),
+      });
+
+      if (response.data.code !== '0x000000') {
+        throw new Error(response.data.description || '识别失败');
+      }
+
+      const recognitionData: IDCardData = response?.data?.data;
+
+      console.log('[IDCardRecognition] 识别成功:', recognitionData);
+
+      message.success('身份证识别成功');
+      await onRecognitionSuccess(recognitionData);
+      handleClose();
+    } catch (err: any) {
+      console.error('[IDCardRecognition] 识别失败:', err);
+      message.error(
+        err.response?.data?.description || err.message || '识别失败'
+      );
+    } finally {
+      setIsRecognizing(false);
+    }
+  };
+
+  /**
+   * 关闭 Modal
+   */
+  const handleClose = () => {
+    console.log('[IDCardRecognition] 关闭 Modal');
+    stopCamera();
+    setCapturedImage(null);
+    setUploadedImage(null);
+    onClose();
+  };
+
+  const uploadProps = {
+    beforeUpload: handleUpload,
+    showUploadList: false,
+    accept: 'image/*',
+  };
+
+  return (
+    <Modal
+      title={
+        <Space>
+          <IdcardOutlined />
+          身份证识别
+        </Space>
+      }
+      open={visible}
+      onCancel={handleClose}
+      footer={null}
+      width={800}
+      centered
+      destroyOnClose
+    >
+      <div style={{ textAlign: 'center' }}>
+        {/* 模式选择 */}
+        <Space style={{ marginBottom: 16 }}>
+          <Button
+            type={mode === 'camera' ? 'primary' : 'default'}
+            onClick={() => setMode('camera')}
+          >
+            自动拍照
+          </Button>
+          <Button
+            type={mode === 'upload' ? 'primary' : 'default'}
+            onClick={() => setMode('upload')}
+          >
+            手动上传
+          </Button>
+        </Space>
+
+        {/* 摄像头模式 */}
+        {mode === 'camera' && (
+          <div style={{ position: 'relative' }}>
+            {!capturedImage && (
+              <>
+                <video
+                  ref={videoRef}
+                  autoPlay
+                  playsInline
+                  muted
+                  style={{
+                    width: '100%',
+                    maxHeight: '400px',
+                    backgroundColor: '#000',
+                    borderRadius: 8,
+                    display: isInitializing ? 'none' : 'block',
+                  }}
+                />
+
+                {isInitializing && (
+                  <div
+                    style={{
+                      width: '100%',
+                      height: '400px',
+                      display: 'flex',
+                      alignItems: 'center',
+                      justifyContent: 'center',
+                      backgroundColor: '#000',
+                      borderRadius: 8,
+                    }}
+                  >
+                    <Spin size="large" tip="正在初始化摄像头..." />
+                  </div>
+                )}
+
+                {!isInitializing && (
+                  <Space style={{ marginTop: 16 }} size="large">
+                    <Button
+                      type="primary"
+                      size="large"
+                      icon={<CameraOutlined />}
+                      onClick={handleCapture}
+                    >
+                      拍照
+                    </Button>
+                    <Button
+                      size="large"
+                      icon={<CloseOutlined />}
+                      onClick={handleClose}
+                    >
+                      取消
+                    </Button>
+                  </Space>
+                )}
+              </>
+            )}
+
+            {capturedImage && (
+              <>
+                <img
+                  src={capturedImage}
+                  alt="拍摄的身份证"
+                  style={{
+                    width: '100%',
+                    maxHeight: '400px',
+                    borderRadius: 8,
+                    objectFit: 'contain',
+                  }}
+                />
+                <Space style={{ marginTop: 16 }} size="large">
+                  <Button
+                    type="primary"
+                    size="large"
+                    icon={<IdcardOutlined />}
+                    onClick={handleRecognize}
+                    loading={isRecognizing}
+                  >
+                    识别身份证
+                  </Button>
+                  <Button
+                    size="large"
+                    icon={<ReloadOutlined />}
+                    onClick={handleRetake}
+                    disabled={isRecognizing}
+                  >
+                    重拍
+                  </Button>
+                  <Button
+                    size="large"
+                    icon={<CloseOutlined />}
+                    onClick={handleClose}
+                    disabled={isRecognizing}
+                  >
+                    取消
+                  </Button>
+                </Space>
+              </>
+            )}
+          </div>
+        )}
+
+        {/* 上传模式 */}
+        {mode === 'upload' && (
+          <div>
+            {!uploadedImage ? (
+              <div
+                style={{
+                  padding: '40px',
+                  border: '2px dashed #d9d9d9',
+                  borderRadius: 8,
+                }}
+              >
+                <Upload {...uploadProps}>
+                  <Space direction="vertical">
+                    <UploadOutlined
+                      style={{ fontSize: 48, color: '#1890ff' }}
+                    />
+                    <div>点击或拖拽身份证图片到此处上传</div>
+                    <div style={{ color: '#999' }}>只支持 PNG 格式</div>
+                  </Space>
+                </Upload>
+              </div>
+            ) : (
+              <>
+                <img
+                  src={uploadedImage}
+                  alt="上传的身份证"
+                  style={{
+                    width: '100%',
+                    maxHeight: '400px',
+                    borderRadius: 8,
+                    objectFit: 'contain',
+                  }}
+                />
+                <Space style={{ marginTop: 16 }} size="large">
+                  <Button
+                    type="primary"
+                    size="large"
+                    icon={<IdcardOutlined />}
+                    onClick={handleRecognize}
+                    loading={isRecognizing}
+                  >
+                    识别身份证
+                  </Button>
+                  <Button
+                    size="large"
+                    onClick={() => setUploadedImage(null)}
+                    disabled={isRecognizing}
+                  >
+                    重新上传
+                  </Button>
+                  <Button
+                    size="large"
+                    icon={<CloseOutlined />}
+                    onClick={handleClose}
+                    disabled={isRecognizing}
+                  >
+                    取消
+                  </Button>
+                </Space>
+              </>
+            )}
+          </div>
+        )}
+      </div>
+    </Modal>
+  );
+};
+
+export default IDCardRecognitionModal;

+ 33 - 10
src/components/IDCardRecognitionModal.tsx

@@ -67,6 +67,10 @@ const IDCardRecognitionModal: React.FC<IDCardRecognitionModalProps> = ({
       const cameraService = CameraServiceFactory.create();
 
       const hasPermission = await cameraService.requestPermission();
+      console.log(
+        '[IDCardRecognition] 检测到环境:',
+        CameraServiceFactory.detectEnvironment()
+      );
       if (!hasPermission) {
         throw new Error('用户拒绝了摄像头权限');
       }
@@ -159,14 +163,23 @@ const IDCardRecognitionModal: React.FC<IDCardRecognitionModalProps> = ({
    * 上传文件处理
    */
   const handleUpload = (file: File) => {
-    const reader = new FileReader();
-    reader.onload = (e) => {
-      const result = e.target?.result as string;
-      setUploadedImage(result);
-      setCapturedImage(null);
-      message.success('图片上传成功');
+    const img = new Image();
+    img.onload = () => {
+      if (img.width > img.height) {
+        const reader = new FileReader();
+        reader.onload = (e: ProgressEvent<FileReader>) => {
+          const result = (e.target as FileReader)?.result as string;
+          setUploadedImage(result);
+          setCapturedImage(null);
+          message.success('图片上传成功');
+        };
+        reader.readAsDataURL(file);
+      } else {
+        message.error('请上传横向身份证照片,竖图将被拒绝');
+      }
+      URL.revokeObjectURL(img.src);
     };
-    reader.readAsDataURL(file);
+    img.src = URL.createObjectURL(file);
     return false; // 阻止自动上传
   };
 
@@ -249,13 +262,21 @@ const IDCardRecognitionModal: React.FC<IDCardRecognitionModalProps> = ({
         <Space style={{ marginBottom: 16 }}>
           <Button
             type={mode === 'camera' ? 'primary' : 'default'}
-            onClick={() => setMode('camera')}
+            onClick={() => {
+              handleRetake();
+              setMode('camera');
+            }}
           >
             自动拍照
           </Button>
           <Button
             type={mode === 'upload' ? 'primary' : 'default'}
-            onClick={() => setMode('upload')}
+            onClick={() => {
+              stopCamera();
+              setUploadedImage(null);
+              setMode('upload');
+            }}
+            disabled={isInitializing}
           >
             手动上传
           </Button>
@@ -379,7 +400,9 @@ const IDCardRecognitionModal: React.FC<IDCardRecognitionModalProps> = ({
                       style={{ fontSize: 48, color: '#1890ff' }}
                     />
                     <div>点击或拖拽身份证图片到此处上传</div>
-                    <div style={{ color: '#999' }}>只支持 PNG 格式</div>
+                    <div style={{ color: '#999' }}>
+                      只支持横向身份证照片(PNG格式)
+                    </div>
                   </Space>
                 </Upload>
               </div>