complete Complete User Request Submission section RAD-1

This commit is contained in:
2026-01-03 19:14:30 +03:30
commit 7137ec5792
393 changed files with 152602 additions and 0 deletions
@@ -0,0 +1,44 @@
<script setup>
import vueDropzone from 'dropzone-vue3'
import {useMediumStore} from "../../store/Medium.js";
const props = defineProps({
uploadEndpoint: {
type: String,
default: '/admin/medium'
}
});
const dropzoneOptions = {
url: props.uploadEndpoint,
thumbnailWidth: 150,
maxFilesize: 20,
dictDefaultMessage: `
<div class="!font-dana">
<span class="font-black text-lg">آپلود رسانه</span>
<p class="font-normal text-xs">حداکثر آپلود فایل 20 مگابایت میباشد</p>
</div>
`,
headers : { "X-CSRF-TOKEN": document.head.querySelector("[name=csrf-token]").content}
}
const MediumStore = useMediumStore();
const MediumSuccess = (response) => {
if (response.xhr.response == 'noFormat'){
alert('فرمت فایل نا معتبر است')
}
MediumStore.mediums.unshift(JSON.parse(response.xhr.response));
console.log(JSON.parse(response.xhr.response))
};
</script>
<template>
<vue-dropzone
ref="myVueDropzone"
id="dropzone"
class="!border-dashed !bg-transparent overflow-y-auto max-h-[12rem]"
:options="dropzoneOptions"
v-on:vdropzone-success="MediumSuccess"
/>
</template>
@@ -0,0 +1,174 @@
<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import {useMediumStore} from "../../store/Medium.js";
import Media from "./Media.vue";
import PaginationAjax from "../PaginationAjax.vue";
import AddMedium from "./AddMedia.vue";
const MediumStore = useMediumStore();
const emit = defineEmits(['update:modelValue', 'remove-item']);
const props = defineProps({
modelValue: { type: [Boolean, Array], required: true },
multiple: { type: Boolean, default: false },
uploadEndpoint: { type: String, default: '/admin/upload-medium' }
});
const selectImage = ref({});
const selectedImages = ref([]);
const isChooseMedium = ref(false);
const visible_modal_mediums = ref(false);
const hasLoaded = ref(false);
watch(() => props.modelValue, (newVal) => {
if (props.multiple) {
selectedImages.value = Array.isArray(newVal) ? newVal : [];
isChooseMedium.value = selectedImages.value.length > 0;
} else {
selectImage.value = newVal;
isChooseMedium.value = newVal && newVal.url ? true : false;
}
}, { immediate: true });
const diselect_medium = () => {
if (props.multiple) {
selectedImages.value = [];
isChooseMedium.value = false;
emit('update:modelValue', []);
} else {
selectImage.value = {};
isChooseMedium.value = false;
emit('update:modelValue', selectImage.value);
}
}
const getMediums = (page = 1) => {
axios.get(`/admin/get-medium?page=${page}`).then(res => {
MediumStore.mediums = res.data.data ?? [];
MediumStore.paginate.total = res.data.total ?? null;
MediumStore.paginate.last_page = res.data.last_page ?? null;
MediumStore.paginate.current_page = res.data.current_page ?? null;
MediumStore.paginate.links = res.data.links ?? null;
MediumStore.paginate.per_page = res.data.per_page ?? null;
hasLoaded.value = true;
})
}
const toggle_select_image = (medium, event) => {
if (props.multiple) {
const isCtrlPressed = event.ctrlKey || event.metaKey;
const existingIndex = selectedImages.value.findIndex(c => c.id === medium.id);
if (existingIndex > -1) {
selectedImages.value.splice(existingIndex, 1);
} else {
if (isCtrlPressed) {
selectedImages.value.push(medium);
} else {
selectedImages.value = [medium];
}
}
} else {
if (selectImage.value != medium) {
selectImage.value = medium;
} else {
selectImage.value = {};
}
}
}
const chooseMedium = () => {
isChooseMedium.value = true;
if (props.multiple) {
emit('update:modelValue', selectedImages.value);
} else {
emit('update:modelValue', selectImage.value);
}
toggle_show_modal();
}
const toggle_show_modal = () => {
visible_modal_mediums.value = !visible_modal_mediums.value;
if (!hasLoaded.value){
getMediums();
}
}
const removeSelectedImage = (index) => {
emit('remove-item', index);
}
</script>
<template>
<div class="card flex justify-center">
<div @click="toggle_show_modal" class="m-auto mt-6 grid grid-cols-a shadow-lg items-center border-3 border-client-gray/5 relative w-[14rem] content-center justify-center
rounded-2xl h-[14rem] cursor-pointer text-center text-client-Fontcolor/90 shadow-client-gray/10">
<div v-if="!isChooseMedium && (!selectImage?.url && selectedImages.length === 0)" class="grid grid-cols-a">
<div class="flex justify-center m-auto bg-client-gray/10 p-2 rounded-2xl">
<i class="fi fi-rr-cloud-upload-alt flex text-client-Fontcolor/80 m-auto text-4xl"></i>
</div>
<div class="absolute -top-3 -right-3 shadow-all p-3 rounded-3xl">
<i class="fi fi-rr-add-image flex text-xl"></i>
</div>
<span class="block text-sm text-client-Fontcolor/80 font-black mt-3">
{{ multiple ? 'انتخاب یا بارگذاری فایل‌ها' : 'انتخاب یا بارگذاری تصویر کاربر' }}
</span>
<span class="block text-xs mt-1">حداکثر آپلود ۲ مگابایت میباشد</span>
</div>
<div v-else-if="multiple && selectedImages.length > 0" class="p-2">
<h1 class="font-bold text-sm">
انتخاب فایل های بیشتر
</h1>
<div class="text-center mt-2">
<span class="text-xs text-gray-500">{{ selectedImages.length }} فایل انتخاب شده</span>
</div>
</div>
<div v-else-if="!multiple && selectImage?.url">
<figure class="h-[13rem] px-4 py-0">
<img class="w-full h-full object-contain" :src="selectImage.url" :alt="selectImage.filename">
</figure>
</div>
</div>
<Dialog v-model:visible="visible_modal_mediums" maximizable modal header="انتخاب رسانه"
:style="{ width: '75vw' , minHeight: '45rem' }"
:pt="{header : '!bg-client-primaryBlue'}"
:breakpoints="{ '1199px': '75vw', '575px': '90vw' }">
<template #header>
<div class="flex items-center justify-between w-full">
<h2 class="font-black text-lg">نمایش همه رسانه</h2>
<div class="flex gap-2 px-6" v-if="(selectImage && selectImage?.id) || (multiple && selectedImages.length > 0)">
<Button @click="chooseMedium" label="انتخاب میکنم" variant="outlined" size="small" severity="" class="" />
</div>
</div>
</template>
<div class="grid grid-cols-1 overflow-y-auto max-h-[12rem]">
<add-medium class="h-full w-full" :upload-endpoint="uploadEndpoint"></add-medium>
</div>
<div id="startModal" class="grid grid-cols-[repeat(auto-fill,_minmax(8rem,11rem))] mt-3 gap-4 justify-center">
<Media :action="false" @click="toggle_select_image(medium, $event)" v-for="(medium , index) in MediumStore.mediums"
:medium="medium" :class="['border-[2px] border-transparent transition-all',
{'!border-[2px] !border-client-primary' : multiple ? selectedImages.some(c => c.id === medium.id) : medium.id == selectImage.id }]" :key="index"></Media>
</div>
<PaginationAjax
:onPaginate="(page) => getMediums(page)"
top-scroll="startModal"
:total="MediumStore.paginate.total"
:current-page="MediumStore.paginate.current_page"
:perPage="MediumStore.paginate.per_page"
:lastPage="MediumStore.paginate.last_page"
:links="MediumStore.paginate.links">
</PaginationAjax>
</Dialog>
</div>
<div v-if="((selectImage && selectImage?.id) || (multiple && selectedImages.length > 0)) && isChooseMedium" class="grid mt-4 grid-cols-a justify-center gap-2">
<Button @click="diselect_medium" variant="outlined" size="small" severity="secondary" class="flex m-auto" label="لغو انتخاب"/>
<div v-if="multiple && selectedImages.length > 0" class="space-y-1">
<div v-for="(image, index) in selectedImages" :key="index" class="flex items-center justify-between bg-gray-100 p-2 rounded text-sm">
<span class="truncate text-xs px-4">{{ image.filename }}</span>
<Button icon="fi fi-rr-trash" size="small" severity="danger" text @click="removeSelectedImage(index)" />
</div>
</div>
</div>
</template>
@@ -0,0 +1,30 @@
<script setup>
import {useMediumStore} from "../../store/Medium.js";
import Pagination from "../Pagination.vue";
import Media from "./Media.vue";
const MediumStore = useMediumStore();
</script>
<template>
<section class="grid grid-cols-1 mt-4">
<div class="grid grid-cols-5 max-xl:grid-cols-4 max-lg:grid-cols-3 max-md:grid-cols-2 gap-4 justify-center max-sm:grid-cols-1">
<Media :action="true" v-for="medium in MediumStore.mediums" :medium="medium" :key="medium.id"
:class="{'active' : medium.id === MediumStore.selected }"></Media>
</div>
<div class="bottom">
<Pagination
:total="MediumStore.paginate.total"
:current-page="MediumStore.paginate.current_page"
:perPage="MediumStore.paginate.per_page"
:lastPage="MediumStore.paginate.last_page"
:links="MediumStore.paginate.links"></Pagination>
</div>
</section>
</template>
<style scoped lang="css">
</style>
@@ -0,0 +1,94 @@
<script setup>
import {ref} from "vue";
import {useMediumStore} from "../../store/Medium.js";
import {Inertia} from "@inertiajs/inertia";
const MediumStore = useMediumStore();
const hasCopyUrl = ref(false);
const props = defineProps({
medium : {
type:Object
},
action : {
type: Boolean,
default: () => false
}
});
const copyImageUrl = (id) => {
let testingCodeToCopy = document.getElementById(`mediumUrlCopy_${id}`);
testingCodeToCopy.setAttribute('type', 'value');
testingCodeToCopy.select();
document.execCommand('copy')
testingCodeToCopy.setAttribute('type', 'hidden');
hasCopyUrl.value = id;
window.getSelection().removeAllRanges();
}
const singleDelete = (id) => {
Inertia.delete(`/admin/medium/${id}`, {
onSuccess: () => {
MediumStore.mediums = MediumStore.mediums.filter(medium => medium.id !== id);
}
}, { replace: false });
}
const getImageUrl = (medium) => {
if (medium.disk === 'public') {
return medium.url;
}
if (medium.disk === 'private') {
return `/admin/medium/${medium.id}/serve`;
}
return medium.url;
}
</script>
<template>
<div class="grid grid-cols-1 bg-white border-[1px] border-gray-100 shadow-all cursor-pointer rounded-2xl">
<input :id="`mediumUrlCopy_${medium.id}`" type="hidden" :value="getImageUrl(medium)"/>
<figure class="p-3 h-[10rem] content-center">
<img class="w-full h-full rounded-2xl object-contain" v-if="medium.mime_type == 'mp4' || medium.mime_type == 'mkv'" src="@/images/musicFolder.png">
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'mp3'" src="@/images/musicFolder.png">
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'application/zip' || medium.mime_type == 'application/rar'" src="@/images/zip.ico">
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'application/pdf'" src="@/images/pdf.webp">
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'application/txt'" src="@/images/txtFormat.png">
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'sql'" src="@/images/1769_-_SQL-512.png">
<img class="w-full h-full object-contain rounded-2xl" v-else :src="getImageUrl(medium)">
</figure>
<div class="px-4 text-xs grid grid-cols-1 gap-2 mb-4">
<div class="grid grid-cols-2a justify-between gap-2 items-center">
<span class="text-[.67rem]">نام فایل:</span>
<span class="text-xs font-light w-full truncate">{{medium.filename}}</span>
</div>
<div class="grid grid-cols-2a justify-between gap-2 items-center">
<span class="text-[.67rem]">سایز فایل:</span>
<span class="text-xs font-light w-full truncate"> {{medium.size}}kb </span>
</div>
<div class="grid grid-cols-2a justify-between gap-2 items-center">
<span class="text-[.67rem]">فرمت فایل:</span>
<span class="text-xs font-light w-full truncate">{{medium.mime_type}}</span>
</div>
<div v-if="action" class="grid grid-cols-1 justify-between gap-2 items-center" :class="{ '!grid-cols-2' : action}">
<Button type="button"
@click="singleDelete(medium.id)" severity="danger"
label="حذف"
outlined
class="!text-xs"
size="small"
:pt="{icon : '!mt-2 !text-xs'}"/>
<Button @click="copyImageUrl(medium.id)" type="button" class="!text-xs"
severity="secondary"
size="small"
:label="hasCopyUrl == medium.id ? '✓ کپی شد ' : 'کپی آدرس'"
:pt="{icon : '!mt-2 !text-xs'}"/>
</div>
</div>
</div>
</template>
<style scoped lang="css">
</style>