Initial commit
This commit is contained in:
296
app/components/media/ImageCard.vue
Normal file
296
app/components/media/ImageCard.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<script setup>
|
||||
import FileActions from '@/components/media/FileActions.vue';
|
||||
|
||||
const props = defineProps({
|
||||
image: Object,
|
||||
index: Number,
|
||||
layoutMode: String,
|
||||
loadImages: Function,
|
||||
deleteImage: Function,
|
||||
viewImage: Function,
|
||||
})
|
||||
|
||||
const {
|
||||
$buildFileUrl,
|
||||
$formatFileSize,
|
||||
$getpath,
|
||||
$patchapi,
|
||||
$snackbar,
|
||||
} = useNuxtApp();
|
||||
|
||||
const url = $buildFileUrl(props.image.file__file);
|
||||
|
||||
// State cho editing image
|
||||
const editingImage = ref(null);
|
||||
const editingImageName = ref("");
|
||||
const editingImageCaption = ref("");
|
||||
const editingImageHashtag = ref("");
|
||||
const editingImageFileInput = ref(null);
|
||||
|
||||
const pendingDeleteImage = ref(null);
|
||||
|
||||
function editImage(image) {
|
||||
editingImage.value = image;
|
||||
editingImageName.value = image.file__name || "";
|
||||
editingImageCaption.value = image.file__caption || "";
|
||||
editingImageHashtag.value = image.file__hashtag || "";
|
||||
}
|
||||
|
||||
async function saveEditImage() {
|
||||
try {
|
||||
await $patchapi("file", {
|
||||
id: editingImage.value.file,
|
||||
name: editingImageName.value,
|
||||
caption: editingImageCaption.value?.trim() || null,
|
||||
hashtag: editingImageHashtag.value?.trim() || null,
|
||||
});
|
||||
|
||||
props.loadImages();
|
||||
$snackbar("Đã cập nhật ảnh thành công", "Thành công", "Success");
|
||||
} catch (error) {
|
||||
console.error("Error updating image", error);
|
||||
$snackbar("Cập nhật ảnh thất bại", "Lỗi", "Error");
|
||||
}
|
||||
|
||||
resetEditRefs();
|
||||
}
|
||||
|
||||
function resetEditRefs() {
|
||||
editingImage.value = null;
|
||||
editingImageName.value = "";
|
||||
editingImageCaption.value = "";
|
||||
editingImageHashtag.value = "";
|
||||
if (editingImageFileInput.value) {
|
||||
editingImageFileInput.value.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadImage(image) {
|
||||
const downloadUrl = `${$getpath()}download/?name=${encodeURIComponent(image.file__file)}&type=file`;
|
||||
window.open(downloadUrl);
|
||||
}
|
||||
|
||||
async function openDeleteImageConfirm(image) {
|
||||
pendingDeleteImage.value = image;
|
||||
const confirmed = window.confirm(`Bạn có chắc chắn muốn xóa ảnh "${image.file__name}"?`);
|
||||
|
||||
if (confirmed) {
|
||||
await props.deleteImage(image);
|
||||
}
|
||||
|
||||
pendingDeleteImage.value = null;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="layoutMode === 'grid'" class="column is-one-third is-one-quarter-widescreen p-2">
|
||||
<div
|
||||
class="card image-card"
|
||||
style="
|
||||
outline: 1px solid hsl(0 0% 0% / 0.2);
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
">
|
||||
<!-- Edit mode -->
|
||||
<div
|
||||
v-if="editingImage?.id === image.id"
|
||||
class="card-content p-2"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Tên ảnh</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageName"
|
||||
>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Mô tả</label>
|
||||
<div class="control">
|
||||
<textarea
|
||||
class="textarea is-small"
|
||||
v-model="editingImageCaption"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14">Hashtag</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageHashtag"
|
||||
>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons is-small mt-2">
|
||||
<button
|
||||
class="button is-small has-text-white is-primary"
|
||||
@click="saveEditImage"
|
||||
>
|
||||
<span>Lưu</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-small is-danger has-text-white"
|
||||
@click="resetEditRefs"
|
||||
>
|
||||
<span>Hủy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- View mode -->
|
||||
<div v-else>
|
||||
<div
|
||||
class="card-image"
|
||||
style="cursor: pointer"
|
||||
@click="props.viewImage(index)"
|
||||
>
|
||||
<figure class="image is-4by3 has-background-black">
|
||||
<nuxt-img
|
||||
:src="url"
|
||||
loading="lazy"
|
||||
:alt="image.file__caption || image.file__name || 'Hình ảnh'"
|
||||
style="object-fit: contain;"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content p-2">
|
||||
<div class="image-meta">
|
||||
<a
|
||||
:href="url"
|
||||
target="_blank"
|
||||
class="fs-14 has-text-weight-semibold is-clipped"
|
||||
>
|
||||
{{ image.file__name || "Hình ảnh" }}
|
||||
</a>
|
||||
<p class="is-size-7 has-text-grey is-clipped">
|
||||
{{ image.file__caption }}
|
||||
</p>
|
||||
</div>
|
||||
<FileActions v-bind="{
|
||||
className: 'is-right mt-2',
|
||||
image,
|
||||
editImage,
|
||||
downloadImage,
|
||||
openDeleteImageConfirm
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<tr v-if="editingImage?.id === image.id">
|
||||
<td colspan="5">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Tên tài liệu</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Mô tả</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageCaption"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Hashtag</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
v-model="editingImageHashtag"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label"> </label>
|
||||
<div class="control">
|
||||
<div class="buttons are-small">
|
||||
<button
|
||||
class="button is-primary"
|
||||
@click="saveEditImage"
|
||||
>
|
||||
Lưu
|
||||
</button>
|
||||
<button
|
||||
class="button is-danger"
|
||||
@click="resetEditRefs"
|
||||
>
|
||||
Hủy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<a
|
||||
class="has-text-weight-semibold has-text-primary"
|
||||
@click="props.viewImage(index)"
|
||||
>
|
||||
{{ image.file__name }}
|
||||
</a>
|
||||
<p class="is-size-7 has-text-grey">
|
||||
{{ $formatFileSize(image.file__size) }}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<span class="is-size-7">{{
|
||||
image.file__caption
|
||||
}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="is-size-7">{{
|
||||
image.file__hashtag
|
||||
}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<p class="is-size-7 has-text-weight-semibold">
|
||||
{{ image.file__user__fullname }}
|
||||
</p>
|
||||
<p class="is-size-7 has-text-grey">
|
||||
{{
|
||||
image.create_time
|
||||
? new Date(image.create_time).toLocaleString(
|
||||
"vi-VN"
|
||||
)
|
||||
: "-"
|
||||
}}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<FileActions v-bind="{
|
||||
image,
|
||||
editImage,
|
||||
downloadImage,
|
||||
openDeleteImageConfirm
|
||||
}" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
Reference in New Issue
Block a user