Files
web/app/components/media/ImageCard.vue
2026-03-02 09:45:33 +07:00

297 lines
7.9 KiB
Vue

<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"> 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">&nbsp;</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>