q-upload 문의 드립니다.
490
投稿した質問数 4
안녕하세요
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 관련 커뮤니티나 포럼에서 유사한 사례를 찾아보시는 것이 도움이 될 수 있습니다.
조언이 도움이 되었길 바랍니다. 추가적인 질문이 있으시면 언제든지 문의해 주세요.
0
전체 소스 첨부 합니다.
첨부파일 부분 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>

콘솔 로그 정보 입니다.
의견 부탁 드립니다.
감사합니다.
ComboBox 관련해서 질문 드립니다.
0
69
2
<g-page class="q-pa-xl"> 했는데도 여백 안생김
0
83
2
quasar intellisense extension 관련 문의
0
263
2
q-tab 오류 문의
0
238
1
q-input과 :rules 속성을 함께 쓸때, items-center 속성이 이상한 현상
0
309
1
Key값으로 Object 사용가능할까요?
0
230
1
npm run build 시 오류 발생합니다
0
460
1
fontawsome 사용법
0
251
1
Quasar 관련 익스텐션
0
216
1
VUE를 이용해서 프로그램 파일을 실행할수 있을까요?
0
410
1
q-input 태그 내용사라짐
0
272
1
q-input 컴포넌트 질문
0
466
1
버튼이 마음대로 크기가 바뀌어요
0
655
1
다크 모드 처리 관련하여 질문드립니다.
0
532
1
script setup 태그 유무에 따라서 페이지 출력이 안됩니다.
0
543
3
vue3 composition api (script setup) 사용 시 전역변수 접근
0
1440
1
한글 실시간 바인딩
2
2532
2
vue3 composition Api 사용시 setup 내부에서도 /src/boot에 설정한 constatns.js 값을 가져올수 있나요?
0
396
1
router.push로 이동 시, 유튜브 페이지의 컨텐츠 영역 안보이는 현상
0
464
1
validation
0
367
1
CORS 설정
0
605
1
질문 있습니다
0
334
1
Helper Classes
0
310
1
Quasar 내장 컴포넌트 색 질문입니다.
0
444
1

