fix(uploader): resolve upload endpoint URL and update ImageUploader props in CreateProperty
deploy-chabokan / deploy (push) Canceled after 0s
deploy-chabokan / deploy (push) Canceled after 0s
This commit is contained in:
@@ -417,8 +417,12 @@ const saveProperty = async () => {
|
||||
|
||||
isSaving.value = true;
|
||||
try {
|
||||
property.value.mainImageUrls = mainImageUrlsWithData.value.map((x) => x.url);
|
||||
property.value.documentUrls = documentUrlsWithData.value.map((x) => x.url);
|
||||
property.value.mainImageUrls = (mainImageUrlsWithData.value || [])
|
||||
.map((x) => (typeof x === 'string' ? x : x.url || x.source || ''))
|
||||
.filter(Boolean);
|
||||
property.value.documentUrls = (documentUrlsWithData.value || [])
|
||||
.map((x) => (typeof x === 'string' ? x : x.url || x.source || ''))
|
||||
.filter(Boolean);
|
||||
property.value.features = serializeFeaturesPayload(featuresValues.value);
|
||||
|
||||
let status = 'failed';
|
||||
@@ -857,7 +861,7 @@ const saveProperty = async () => {
|
||||
<i class="pi pi-file-pdf text-xl"></i>
|
||||
<span>اسناد و مدارک تصویری / پیدیاف ملک</span>
|
||||
</div>
|
||||
<ImageUploader v-model="documentUrlsWithData" uploadUrl="/api/files/admin/upload-file" @uploaded="onDocumentUploaded" />
|
||||
<ImageUploader v-model="documentUrlsWithData" bucketName="documents" :maxFiles="10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -895,7 +899,7 @@ const saveProperty = async () => {
|
||||
<i class="pi pi-image text-xl"></i>
|
||||
<span>تصاویر ملک</span>
|
||||
</div>
|
||||
<ImageUploader v-model="mainImageUrlsWithData" uploadUrl="/api/files/admin/upload-file" @uploaded="onMainImageUploaded" />
|
||||
<ImageUploader v-model="mainImageUrlsWithData" bucketName="properties" :maxFiles="10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,42 +1,48 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div class="image-uploader-wrapper">
|
||||
<FilePond
|
||||
ref="pond"
|
||||
name="filepond-image"
|
||||
:max-files="maxFiles"
|
||||
store-as-file="true"
|
||||
:allow-multiple="allowMultiple"
|
||||
:accepted-file-types="acceptedFileTypes"
|
||||
:server="{
|
||||
process: {
|
||||
url: API_URL + uploadEndpoint,
|
||||
url: resolvedUploadUrl,
|
||||
method: 'POST',
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
authorization: 'Bearer ' + Cookies.get('bm-token')
|
||||
},
|
||||
headers: authHeaders,
|
||||
onload: (response) => handleProcessResponse(response)
|
||||
},
|
||||
revert: {
|
||||
url: API_URL + deleteEndpoint,
|
||||
url: resolvedDeleteUrl,
|
||||
method: 'DELETE',
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
authorization: 'Bearer ' + Cookies.get('bm-token')
|
||||
},
|
||||
headers: authHeaders,
|
||||
onload: (response) => handleRevertResponse(response)
|
||||
},
|
||||
load: (source, load, error, progress, abort, headers) => {
|
||||
fetch(source)
|
||||
.then((res) => res.blob())
|
||||
.then(load)
|
||||
.catch((err) => {
|
||||
console.warn('FilePond failed to load existing image:', source, err);
|
||||
error(err.message);
|
||||
});
|
||||
}
|
||||
}"
|
||||
label-idle="برای آپلود کلیک کنید"
|
||||
:files="initialFiles"
|
||||
@removefile="handleRemoveFile"
|
||||
label-idle="برای آپلود کلیک کنید یا فایل را بکشید و رها کنید"
|
||||
label-tap-to-undo="برای حذف کلیک کنید"
|
||||
label-file-processing="در حال آپلود"
|
||||
label-file-processing-complete="آپلود فایل تکمیل شد"
|
||||
:allow-multiple="true"
|
||||
v-model="uploadedFiles"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, defineProps, computed } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import Cookies from 'js-cookie';
|
||||
import vueFilePond from 'vue-filepond';
|
||||
import 'filepond/dist/filepond.min.css';
|
||||
@@ -46,17 +52,33 @@ import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
|
||||
|
||||
// Define props
|
||||
const props = defineProps({
|
||||
bucketName: {
|
||||
type: String,
|
||||
default: 'properties'
|
||||
},
|
||||
uploadEndpoint: {
|
||||
type: String,
|
||||
required: true
|
||||
default: ''
|
||||
},
|
||||
uploadUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
deleteEndpoint: {
|
||||
type: String,
|
||||
required: true
|
||||
default: ''
|
||||
},
|
||||
deleteUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxFiles: {
|
||||
type: Number,
|
||||
default: 1
|
||||
default: 10
|
||||
},
|
||||
allowMultiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
acceptedFiles: {
|
||||
type: String,
|
||||
@@ -64,58 +86,154 @@ const props = defineProps({
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'public'
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
// Setup reactive variables
|
||||
const API_URL = import.meta.env.VITE_API_URL;
|
||||
const uploadedFiles = ref([...props.modelValue]);
|
||||
const emit = defineEmits(['update:modelValue', 'upload', 'uploaded', 'delete']);
|
||||
|
||||
// Define FilePond component
|
||||
const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview);
|
||||
const emit = defineEmits(['update:modelValue', 'upload', 'delete']);
|
||||
const pond = ref(null);
|
||||
|
||||
const acceptedFileTypes = computed(() => {
|
||||
if (!props.acceptedFiles) return undefined;
|
||||
return props.acceptedFiles.split(',').map((s) => s.trim());
|
||||
});
|
||||
|
||||
const API_URL = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '');
|
||||
|
||||
const authHeaders = computed(() => {
|
||||
const token = Cookies.get('bm-token') || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : '') || '';
|
||||
return token ? { authorization: `Bearer ${token}` } : {};
|
||||
});
|
||||
|
||||
const resolvedUploadUrl = computed(() => {
|
||||
let endpoint = props.uploadUrl || props.uploadEndpoint;
|
||||
if (!endpoint || endpoint === '/api/files/admin/upload-file') {
|
||||
endpoint = `/api/files/bucket/upload-single-filepond/${props.bucketName}`;
|
||||
}
|
||||
if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
|
||||
return endpoint;
|
||||
}
|
||||
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
||||
return `${API_URL}${cleanEndpoint}`;
|
||||
});
|
||||
|
||||
const resolvedDeleteUrl = computed(() => {
|
||||
let endpoint = props.deleteUrl || props.deleteEndpoint;
|
||||
if (!endpoint) {
|
||||
endpoint = `/api/files/bucket/delete-file/${props.bucketName}`;
|
||||
}
|
||||
if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
|
||||
return endpoint;
|
||||
}
|
||||
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
||||
return `${API_URL}${cleanEndpoint}`;
|
||||
});
|
||||
|
||||
const initialFiles = computed(() => {
|
||||
if (!Array.isArray(props.modelValue)) return [];
|
||||
return props.modelValue
|
||||
.map((item) => {
|
||||
const url = typeof item === 'string' ? item : item.url || item.source || '';
|
||||
if (!url) return null;
|
||||
return {
|
||||
source: url,
|
||||
options: {
|
||||
type: 'local'
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
});
|
||||
|
||||
// Handle server responses for file processing
|
||||
function handleProcessResponse(response) {
|
||||
if (props.mode === 'public') {
|
||||
const data = JSON.parse(response);
|
||||
const publicUrl = data.data[0].fileRecord.publicUrl;
|
||||
const fileName = data.data[0].fileRecord.fileName;
|
||||
uploadedFiles.value.push(publicUrl);
|
||||
emit('update:modelValue', uploadedFiles);
|
||||
emit('upload', uploadedFiles);
|
||||
return publicUrl;
|
||||
} else {
|
||||
const data = JSON.parse(response);
|
||||
const privateUrl = data.data[0].fileRecord.privateUrl;
|
||||
const fileName = data.data[0].fileRecord.fileName;
|
||||
uploadedFiles.value.push(privateUrl);
|
||||
emit('update:modelValue', uploadedFiles);
|
||||
emit('upload', uploadedFiles);
|
||||
return privateUrl;
|
||||
let parsed;
|
||||
try {
|
||||
parsed = typeof response === 'string' ? JSON.parse(response) : response;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse file upload response:', e);
|
||||
return '';
|
||||
}
|
||||
|
||||
let fileRecord = null;
|
||||
if (Array.isArray(parsed?.data)) {
|
||||
fileRecord = parsed.data[0]?.fileRecord || parsed.data[0];
|
||||
} else if (parsed?.data?.fileRecord) {
|
||||
fileRecord = parsed.data.fileRecord;
|
||||
} else if (parsed?.data) {
|
||||
fileRecord = parsed.data;
|
||||
} else {
|
||||
fileRecord = parsed;
|
||||
}
|
||||
|
||||
const fileUrl =
|
||||
props.mode === 'private'
|
||||
? fileRecord?.privateUrl || fileRecord?.fileURL || fileRecord?.url || ''
|
||||
: fileRecord?.publicUrl || fileRecord?.fileURL || fileRecord?.url || '';
|
||||
|
||||
const fileName = fileRecord?.fileName || fileRecord?.name || (fileUrl ? fileUrl.split('/').pop() : 'file');
|
||||
|
||||
const newItem = {
|
||||
name: fileName,
|
||||
url: fileUrl,
|
||||
progress: 100,
|
||||
bucketName: props.bucketName
|
||||
};
|
||||
|
||||
const currentList = Array.isArray(props.modelValue) ? [...props.modelValue] : [];
|
||||
const isStringArray = currentList.length > 0 && typeof currentList[0] === 'string';
|
||||
currentList.push(isStringArray ? fileUrl : newItem);
|
||||
|
||||
emit('update:modelValue', currentList);
|
||||
emit('upload', newItem);
|
||||
emit('uploaded', newItem);
|
||||
|
||||
return fileName || fileUrl;
|
||||
}
|
||||
|
||||
// Handle server responses for file revert
|
||||
function handleRevertResponse(response) {
|
||||
const data = JSON.parse(response);
|
||||
const fileName = data.data;
|
||||
// Find and remove the file name from uploadedFiles
|
||||
const fileIndex = uploadedFiles.value.findIndex((name) => name === fileName);
|
||||
if (fileIndex !== -1) {
|
||||
uploadedFiles.value.splice(fileIndex, 1);
|
||||
emit('update:modelValue', uploadedFiles);
|
||||
emit('delete', uploadedFiles);
|
||||
let deletedIdentifier = '';
|
||||
try {
|
||||
const parsed = typeof response === 'string' ? JSON.parse(response) : response;
|
||||
deletedIdentifier = parsed?.data || parsed?.fileName || response;
|
||||
} catch (e) {
|
||||
deletedIdentifier = response;
|
||||
}
|
||||
|
||||
return fileName;
|
||||
removeFromFileList(deletedIdentifier);
|
||||
emit('delete', deletedIdentifier);
|
||||
return deletedIdentifier;
|
||||
}
|
||||
|
||||
function handleRemoveFile(error, fileItem) {
|
||||
if (error) return;
|
||||
const identifier = fileItem?.serverId || fileItem?.source || fileItem?.filename || '';
|
||||
if (identifier) {
|
||||
removeFromFileList(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromFileList(identifier) {
|
||||
if (!Array.isArray(props.modelValue) || !identifier) return;
|
||||
const updated = props.modelValue.filter((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return item !== identifier && !item.includes(identifier);
|
||||
}
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
const u = item.url || item.source || item.name || '';
|
||||
return u !== identifier && !u.includes(identifier);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
emit('update:modelValue', updated);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user