[Vue 프론트엔드] OrderSystem 실습: 상품 등록 폼과 FormData 활용

이지연·2026년 2월 15일
post-thumbnail

Repository URL : https://github.com/jiyean99/order-system-fe

1. 상품 등록 폼 구성 (Vuetify v-form + v-file-input)

Vuetify의 v-form, v-card, v-text-field를 활용해 깔끔한 상품 등록 UI를 구성함.

<v-text-field label="상품명" v-model="name"/>
<v-text-field label="카테고리" v-model="category"/>
<v-text-field label="가격" v-model.number="price"/>
<v-text-field label="재고수량" v-model.number="stockQuantity"/>
<v-file-input 
    label="상품이미지"
    @change="handleFileUpload"
    multiple
/>

핵심:

  • v-model.number: 숫자 입력 시 자동으로 Number 타입 변환
  • v-file-input: 파일 업로드 처리, multiple로 다중 선택 가능

2. 파일 업로드 핸들러 구현

handleFileUpload(event) {
    // event.target.files[0]으로 첫 번째 파일만 선택
    this.productImage = event.target.files[0];
}
  • 다중 업로드 지원하나 현재는 첫 번째 파일만 저장함
  • event.target.files는 HTML <input type="file">의 FileList 객체임

3. FormData를 통한 멀티파트 데이터 전송

이미지 업로드 시 JSON으로는 전송 불가하므로 FormData 객체를 사용함.

async createProduct() {
    const data = new FormData();
    data.append("name", this.name);
    data.append("category", this.category);
    data.append("price", this.price);
    data.append("stockQuantity", this.stockQuantity);
    
    // 이미지 필수 아님 → 백엔드에서 null 허용 처리
    if (this.productImage) {
        data.append("productImage", this.productImage);
    }
    
    await axios.post(`${process.env.VUE_APP_API_BASE_URL}/product/create`, data);
    this.$router.push("/product/list");
}

FormData의 장점

JSON: { name: "상품1", image: FileObject }  ❌ 불가능
FormData: name=상품1&productImage=File   ✅ 가능

중요: productImage를 append하지 않으면 백엔드 DTO에서 null로 매핑되지만, null을 명시적으로 append하면 타입 에러 발생하므로 조건부 처리함.

4. Axios 설정 (이미지 업로드용)

main.js에서 이미 설정한 Axios Interceptor가 자동으로 토큰을 포함함.

// 자동으로 적용됨
config.headers['Authorization'] = `Bearer ${accessToken}`;
// Content-Type은 FormData일 때 브라우저가 자동으로 multipart/form-data로 설정

5. 백엔드 연동 시 주의사항

백엔드 DTO 예시 (Spring Boot)

public class ProductCreateDto {
    private String name;
    private String category;
    private Integer price;
    private Integer stockQuantity;
    private MultipartFile productImage;  // null 허용
}

Controller

@PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> create(@Valid @RequestPart ProductCreateDto dto) {
    // productImage가 null이어도 정상 처리
}

6. 에러 처리 및 사용자 피드백

현재는 간단한 console.log(e)로 처리했으나 실제로는:

try {
    await axios.post(...);
    this.$router.push("/product/list");
} catch (e) {
    if (e.response?.status === 400) {
        alert("입력값을 확인해주세요");
    } else {
        alert("상품 등록에 실패했습니다");
    }
}

7. 전체 동작 흐름

1. 폼 입력 → v-model로 data 바인딩
2. 파일 선택 → handleFileUpload → productImage 저장
3. [등록] 클릭 → FormData 생성 → /product/create POST
4. 성공 → 상품목록 페이지 이동
5. 실패 → 콘솔 출력 (개선 필요)

이 구현으로 이미지 포함 상품 등록이 완성됨. FormData의 조건부 append 처리와 v-model.number 활용이 핵심 포인트임.

profile
Eazy하게

0개의 댓글