Files
Novault-Frontend-web/src/components/transaction/ImageAttachment
2026-01-25 20:12:33 +08:00
..
2026-01-25 20:12:33 +08:00
2026-01-25 20:12:33 +08:00
2026-01-25 20:12:33 +08:00
2026-01-25 20:12:33 +08:00
2026-01-25 20:12:33 +08:00
2026-01-25 20:12:33 +08:00

ImageAttachment Component

Overview

The ImageAttachment component provides a user interface for managing image attachments on transactions. It displays images in a thumbnail grid with add/delete functionality and enforces image count limits.

Features

  • Image Thumbnail Grid: Displays uploaded images in a 3-column grid layout
  • Add Images: File input for selecting images from device (supports JPEG, PNG, HEIC)
  • Delete Images: Remove button overlay on each thumbnail
  • Image Count Display: Shows current count vs maximum (e.g., "2 / 9")
  • Limit Warning: Displays warning when approaching the 9-image limit (at 7+ images)
  • Image Preview: Click on thumbnails to preview full-size images
  • Disabled State: Supports read-only mode

Requirements Validated

This component validates the following requirements from the accounting-feature-upgrade spec:

  • 4.1: Transaction form displays image attachment entry button
  • 4.2: Opens image selector, supports album selection or camera
  • 4.5: Shows image thumbnail preview after upload
  • 4.7: Delete button removes image attachment
  • 4.9: Limits single transaction to max 9 images
  • 4.12: Shows prompt when exceeding limit

Usage

import { ImageAttachment } from './components/transaction/ImageAttachment';
import type { TransactionImage } from './types';

function TransactionForm() {
  const [images, setImages] = useState<TransactionImage[]>([]);

  const handleAddImage = async (file: File) => {
    try {
      const uploadedImage = await uploadImage(transactionId, file, 'medium');
      setImages([...images, uploadedImage]);
    } catch (error) {
      console.error('Failed to upload image:', error);
    }
  };

  const handleRemoveImage = async (imageId: number) => {
    try {
      await deleteImage(transactionId, imageId);
      setImages(images.filter(img => img.id !== imageId));
    } catch (error) {
      console.error('Failed to delete image:', error);
    }
  };

  const handlePreviewImage = (index: number) => {
    // Open image preview modal/fullscreen
    setPreviewIndex(index);
    setShowPreview(true);
  };

  return (
    <ImageAttachment
      images={images}
      onAdd={handleAddImage}
      onRemove={handleRemoveImage}
      onPreview={handlePreviewImage}
      compressionLevel="medium"
    />
  );
}

Props

Prop Type Required Default Description
images TransactionImage[] Yes - Array of uploaded images
onAdd (file: File) => void Yes - Callback when user selects a file to upload
onRemove (imageId: number) => void Yes - Callback when user clicks delete button
onPreview (index: number) => void Yes - Callback when user clicks on an image thumbnail
compressionLevel 'low' | 'medium' | 'high' No 'medium' Image compression level (for display purposes)
disabled boolean No false Disables add/delete/preview interactions
className string No '' Additional CSS class names

Image Constraints

The component enforces the following constraints (defined in imageService.ts):

  • Maximum Images: 9 images per transaction
  • Maximum File Size: 10MB per image
  • Allowed Formats: JPEG, PNG, HEIC

Behavior

Adding Images

  1. User clicks the "添加图片" (Add Image) button
  2. File input opens with accept="image/jpeg,image/png,image/heic" and multiple attributes
  3. User selects one or more images
  4. Component validates:
    • Current count + new count ≤ 9
    • If validation fails, shows alert: "最多添加9张图片"
  5. For each valid file, calls onAdd(file) callback
  6. Parent component handles upload and updates images prop

Deleting Images

  1. User hovers over an image thumbnail
  2. Delete button (×) appears in top-right corner
  3. User clicks delete button
  4. Component calls onRemove(imageId) callback
  5. Parent component handles deletion and updates images prop

Previewing Images

  1. User clicks on an image thumbnail
  2. Component calls onPreview(index) callback with the image index
  3. Parent component handles showing full-screen preview

Warning Display

  • When images.length >= 7, displays warning: "接近图片数量限制" (Approaching image count limit)
  • Warning appears in orange color with alert icon
  • Helps users avoid hitting the hard limit

Disabled State

When disabled={true}:

  • Add button is hidden
  • Delete buttons are hidden
  • Preview clicks are ignored
  • Component is in read-only mode

Testing

The component has comprehensive test coverage:

Unit Tests (21 tests)

Located in ImageAttachment.test.tsx:

  • Image attachment entry and selection (3 tests)
  • Image thumbnail preview (3 tests)
  • Delete button functionality (4 tests)
  • Image count limit (3 tests)
  • Limit exceeded prompt (2 tests)
  • Disabled state (2 tests)
  • Edge cases (3 tests)
  • Custom className (1 test)

Property-Based Tests (6 tests)

Located in ImageAttachment.property.test.tsx:

  • Property 8: Image deletion consistency (validates Requirement 4.7)
  • Image count display consistency (validates Requirement 4.9)
  • Add button visibility based on count (validates Requirements 4.9, 4.12)
  • Warning display when approaching limit (validates Requirements 4.9, 4.12)
  • Delete button count matches image count (validates Requirement 4.7)
  • Preview callback receives correct index (validates Requirement 4.6)

All tests run 100 iterations to verify properties hold across diverse inputs.

Styling

The component uses CSS modules with the following key classes:

  • .image-attachment: Main container
  • .image-attachment__grid: 3-column grid layout
  • .image-attachment__item: Individual image thumbnail container
  • .image-attachment__thumbnail: Image element
  • .image-attachment__delete: Delete button overlay
  • .image-attachment__add: Add button
  • .image-attachment__info: Info section with count and warning
  • .image-attachment__count: Image count display
  • .image-attachment__warning: Warning message

Accessibility

  • Add button has aria-label="添加图片"
  • Delete buttons have aria-label="删除图片"
  • Images have alt attributes with file names
  • Keyboard navigation supported (buttons are focusable)
  • ImagePreview: Full-screen image preview component (task 11.2)
  • TransactionForm: Parent form that uses this component
  • imageService.ts: Handles image upload, compression, and validation
  • IMAGE_CONSTRAINTS: Defines max images, max size, allowed types
  • COMPRESSION_SETTINGS: Defines compression levels

Future Enhancements

  • Drag-and-drop file upload
  • Image reordering via drag-and-drop
  • Batch delete functionality
  • Image cropping/editing
  • Progress indicators during upload
  • Thumbnail lazy loading for performance