인프런 커뮤니티 질문&답변

joohyun kim님의 프로필 이미지
joohyun kim

작성한 질문수

퀘이사(Quasar) 완벽 마스터: Vue 프론트 웹을 빠르게 만들고 싶다면! (Based Vue3)

Quasar Plugins

q-upload 문의 드립니다.

작성

·

302

0

안녕하세요
quasar 강좌를 듣고
파일 업로드 관련하여 q-upload  를 사용 하여 파일 업로드를 진행 하였습니다.
백엔드는 fastapi 를 사용 하였으며
소스는 아래와 같습니다.

<q-uploader
        class="full-width"
        label="첨부파일 (for <78k size)"
        no-thumbnails
        v-model="files"
        accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx, .mp4"
        multiple
        hide-upload-btn
        auto-upload
        :factory="handleFileAdded"
        :upload_progress-label="uploadProgress"
        @rejected="onRejected"
        @removed="handleFileRemoved"
      />


 

<script setup> 부분
const files = ref([]); // 업로드된 파일 목록을 담을 배열
const uploadFile2 = async (file, uploadId, fileKey) => {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('uploadId', uploadId);
  formData.append('fileKey', fileKey);

  try {
    const response = await axios.post(
      `http://127.0.0.1:8000/api/v1/attachFile/Create`,
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: `Bearer ${localStorage.getItem('auth/access_token')}`,
        },
        onUploadProgress: function (progressEvent) {
          const { loaded, total } = progressEvent;
          const progress = Math.round((loaded * 100) / total);
          uploadProgress.value = progress;
          console.log('uploadProgress.value', uploadProgress.value);
          console.log(`Upload Progress: ${progress}%`); 
        },
      },
    ); 
    return response.data;
  } catch (error) {
    console.error('Error uploading file:', error);
    throw error;
  }
};

const handleFileAdded = async newFiles => {
  for (const file of newFiles) {
    try {
      const attachFile = await uploadFile2(file, uploadId, file.__key);
      console.log(attachFile.attach_id);
      console.log(`File uploaded successfully:`, attachFile);

      files.value.push(file);
    } catch (error) {
      console.error(`Error uploading file ${file.name}:`, error);
    }
  }
};

 

실행 하면 파일은 정상적으로 백엔드 서버에 등록되고
등록된 파일 정보도 리턴되어 잘 받아 집니다.

하지만 주의 표시아이콘과 파일 업로드 상태가 0% 로 표시 됩니다.


선배님들의 조언 부탁 드립니다.

감사합니다.

답변 1

0

안녕하세요, 인프런 AI 인턴입니다.

파일 업로드 상태가 0%로 표시되고 주의 표시 아이콘이 나타나는 문제는 q-uploader의 파일 업로드 상태 업데이트 로직에서 발생할 가능성이 높습니다. q-uploader 컴포넌트는 업로드 진행 상태를 반영하기 위해 내부적으로 상태 관리를 수행합니다. 사용자 정의 업로드 함수에서 업로드 진행 상태를 q-uploader에 제대로 반영하지 않으면 업로드 진행률이 0%로 표시될 수 있습니다.

q-uploader:factory prop에 할당된 handleFileAdded 함수 내부에서 업로드 진행률을 관리하는 로직을 확인해보세요. 업로드 진행 상태(progress)를 q-uploader 컴포넌트에 반영하기 위해서는 factory 함수에서 업로드 진행률을 업데이트하는 방식을 q-uploader에 맞게 구현해야 합니다.

실질적으로 업로드 진행 상태를 업데이트하기 위해서는 uploadProgress 변수를 적절히 업데이트하고 있지만, 이 업데이트가 q-uploader에서 인식할 수 있는 방식으로 연동되어야 합니다. 공식 문서나 q-uploader의 구현 예제를 참조하여 업로드 진행률을 제대로 반영하는 방법을 확인해 보세요.

또한, onUploadProgress 이벤트 핸들러가 올바르게 설정되어 있음에도 불구하고 업로드 상태가 반영되지 않는다면, 업로드 진행 상태 업데이트 로직에 문제가 있을 수 있으므로 해당 부분의 코드를 다시 한번 검토해 보시기 바랍니다.

질문하신 내용에 직접적으로 대답할 수 있는 정보가 제공된 문서가 없어 정확한 해결 방안을 제시하기는 어렵지만, 일반적인 조언을 드리자면, q-uploader의 사용 방법과 관련된 공식 문서를 참고하시거나 Quasar 관련 커뮤니티나 포럼에서 유사한 사례를 찾아보시는 것이 도움이 될 수 있습니다.

조언이 도움이 되었길 바랍니다. 추가적인 질문이 있으시면 언제든지 문의해 주세요.

joohyun kim님의 프로필 이미지
joohyun kim
질문자

전체 소스 첨부 합니다.

image첨부파일 부분 q-upload 관련 해서 개별 항목의 Status: failed 가 출력되고
전체 항목부분에도 0.00% 로 출력됩니다.
페이지 전체 소스 첨부 합니다.
선배님들의 의견 부탁 드립니다.

<template>
  <q-form @submit.prevent="handleSubmit">
    <q-card-section class="q-gutter-y-sm">
      <q-input
        v-model="titleModel"
        outlined
        placeholder="제목"
        :rules="[validateRequired]"
        counter
        maxlength="40"
      />
      <q-select
        v-model="isActive"
        outlined
        :options="options"
        emit-value
        map-options
        option-value="value"
        option-label="label"
      >
        <template v-slot:no-option>
          <q-item>
            <q-item-section class="text-grey-7"> 선택하세요. </q-item-section>
          </q-item>
        </template>
      </q-select>
      <!-- <q-input
        v-model="contentModel"
        type="textarea"
        outlined
        placeholder="내용을 작성해주세요~!"
      /> -->
      <TiptapEditor v-model="contentModel" />

      <q-uploader
        class="full-width"
        multiple
        hide-upload-btn
        auto-upload
        :factory="handleFileAdded"
        @rejected="onRejected"
        @removed="handleFileRemoved"
      >
        <template v-slot:list="scope">
          <q-list separator>
            <q-item v-for="file in scope.files" :key="file.__key">
              <q-item-section>
                <q-item-label class="full-width ellipsis">
                  {{ file.name }}
                </q-item-label>

                <q-item-label caption>
                  Status: {{ file.__status }}
                </q-item-label>

                <q-item-label caption>
                  {{ file.__sizeLabel }} / {{ file.__progressLabel }}
                </q-item-label>
              </q-item-section>

              <q-item-section v-if="file.__img" thumbnail class="gt-xs">
                <img :src="file.__img.src" />
              </q-item-section>

              <q-item-section top side>
                <q-btn
                  class="gt-xs"
                  size="12px"
                  flat
                  dense
                  round
                  icon="delete"
                  @click="scope.removeFile(file)"
                />
              </q-item-section>
            </q-item>
          </q-list>
        </template>
      </q-uploader>
    </q-card-section>

    <q-separator />
    <q-card-actions align="right">
      <slot name="actions">
        <q-btn flat label="취소하기" v-close-popup />
        <q-btn type="submit" flat label="저장하기" color="primary" />
      </slot>
    </q-card-actions>
  </q-form>
</template>

<script setup>
import { ref, computed } from 'vue';
import { validateRequired } from 'src/utils/validate-rules';
import TiptapEditor from 'src/components/tiptap/TiptapEditor.vue';
import { uploadFile, removeFile } from 'src/services';
import { useQuasar } from 'quasar';
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';

const $q = useQuasar();

const uploadId = uuidv4();
console.log(uploadId);
const props = defineProps({
  listTitle: {
    type: String,
  },
  listContent: {
    type: String,
  },
  isActive: {
    type: Boolean,
  },
});

const options = ref([
  { label: '공개', value: true },
  { label: '비밀글', value: false },
]);

const emit = defineEmits([
  'update:listTitle',
  'update:listContent',
  'update:isActive',
  'submit',
]);

const titleModel = computed({
  get: () => props.listTitle,
  set: val => emit('update:listTitle', val),
});
const contentModel = computed({
  get: () => props.listContent,
  set: val => emit('update:listContent', val),
});
const isActive = computed({
  get: () => props.isActive,
  set: val => emit('update:isActive', val),
});

const handleSubmit = () => {
  if (!contentModel.value) {
    $q.notify('내용을 작성하세요.');
    return;
  }
  emit('submit');
};

const onRejected = rejectedEntries => {
  console.log('Rejected files:', rejectedEntries);
  $q.notify({
    type: 'negative',
    message: `${rejectedEntries.length} file(s) were rejected due to size or type constraints`,
  });
};

const files = ref([]); // 업로드된 파일 목록을 담을 배열
const uploadProgress = ref('0%');
const isUploading = ref(false);

const checkFileSize = files => {
  return files.filter(file => file.size < 78000);
};

const updateFileStatus = (file, status, uploadedSize) => {
  console.log(`File ${file.name} status updated to: ${status}`);
  console.log(`Uploaded size: ${uploadedSize}`);

  file.__status = status;
  file.__uploaded = uploadedSize;
  file.__progress = (uploadedSize / file.size) * 100;

  // 여기서 파일 상태를 업데이트하거나 처리할 수 있습니다.
  // 예를 들어 Vuex store에 상태를 업데이트하거나 UI를 업데이트하는 등의 작업을 수행할 수 있습니다.
};

const uploadFile2 = async (file, uploadId, fileKey) => {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('uploadId', uploadId);
  formData.append('fileKey', fileKey);

  try {
    const response = await axios.post(
      'http://127.0.0.1:8000/api/v1/attachFile/Create',
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: `Bearer ${localStorage.getItem('auth/access_token')}`,
        },
        onUploadProgress: function (progressEvent) {
          isUploading.value = true;
          const progress = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total,
          );
          uploadProgress.value = `${progress}%`;

          // Update file progress
          file.__progress = progress;
          file.__progressLabel = `${progress}%`;
        },
      },
    );

    // Check the server response for success
    console.log('response.status::::::', response.status);
    if (response.status === 200 && response.data) {
      updateFileStatus(file, 'uploaded', file.size); // Set file status to uploaded
    } else {
      updateFileStatus(file, 'failed', 0); // Set file status to failed
    }

    return response.data;
  } catch (error) {
    console.error('Error uploading file:', error);
    updateFileStatus(file, 'failed', 0);
    throw error;
  }
};
const handleFileAdded = async newFiles => {
  for (const file of newFiles) {
    if (file.size >= 78000) {
      // Assuming you have a size limit of 78 KB
      $q.notify('File size must be less than 78 KB');
      continue; // Skip this file
    }
    try {
      const attachFile = await uploadFile2(file, uploadId, file.__key);
      console.log(`File uploaded successfully:`, attachFile);
      files.value.push(file);
    } catch (error) {
      console.error(`Error uploading file ${file.name}:`, error);
    }
  }
};

const handleFileRemoved = async removedFiles => {
  // 파일이 제거될 때 실행되는 함수
  console.log('Removed files:', removedFiles);
  for (const removedFile of removedFiles) {
    try {
      // 서버에서 파일 제거
      const deleted = await removeFile(removedFile.__key, uploadId);
      if (deleted) {
        // 서버에서 파일이 성공적으로 삭제된 경우 로컬 상태에서도 해당 파일 제거
        const index = files.value.findIndex(
          file => file.__key === removedFile.__key,
        );
        if (index !== -1) {
          files.value.splice(index, 1);
        }
        console.log(
          `File ${removedFile.name} removed successfully from server.`,
        );
      } else {
        console.error(`Error removing file ${removedFile.name} from server.`);
      }
    } catch (error) {
      console.error(
        `Error removing file ${removedFile.name} from server:`,
        error,
      );
    }
  }
};
</script>

<style lang="scss" scoped></style>

image
콘솔 로그 정보 입니다.
의견 부탁 드립니다.
감사합니다.

joohyun kim님의 프로필 이미지
joohyun kim

작성한 질문수

질문하기