feat: add media preview
This commit is contained in:
@@ -142,7 +142,7 @@ func (ctl *posts) Delete(ctx fiber.Ctx, id int64) error {
|
|||||||
|
|
||||||
type PostItem struct {
|
type PostItem struct {
|
||||||
*model.Posts
|
*model.Posts
|
||||||
Medias []*models.MediaItem `json:"medias"`
|
Medias []*model.Medias `json:"medias"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show posts by id
|
// Show posts by id
|
||||||
@@ -161,15 +161,7 @@ func (ctl *posts) Show(ctx fiber.Ctx, id int64) (*PostItem, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &PostItem{
|
return &PostItem{
|
||||||
Posts: post,
|
Posts: post,
|
||||||
Medias: lo.Map(medias, func(media *model.Medias, _ int) *models.MediaItem {
|
Medias: medias,
|
||||||
return &models.MediaItem{
|
|
||||||
ID: media.ID,
|
|
||||||
Name: media.Name,
|
|
||||||
UploadTime: media.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
FileSize: media.Size,
|
|
||||||
MimeType: media.MimeType,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MediaItem struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
UploadTime string `json:"upload_time"`
|
|
||||||
FileSize int64 `json:"file_size"`
|
|
||||||
MimeType string `json:"media_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type mediasModel struct {
|
type mediasModel struct {
|
||||||
log *logrus.Entry `inject:"false"`
|
log *logrus.Entry `inject:"false"`
|
||||||
@@ -91,19 +83,8 @@ func (m *mediasModel) List(ctx context.Context, pagination *requests.Pagination,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert model.Medias to MediaItem
|
|
||||||
mediaItems := lo.Map(medias, func(media model.Medias, _ int) *MediaItem {
|
|
||||||
return &MediaItem{
|
|
||||||
ID: media.ID,
|
|
||||||
Name: media.Name,
|
|
||||||
UploadTime: media.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
FileSize: media.Size,
|
|
||||||
MimeType: media.MimeType,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return &requests.Pager{
|
return &requests.Pager{
|
||||||
Items: mediaItems,
|
Items: medias,
|
||||||
Total: count,
|
Total: count,
|
||||||
Pagination: *pagination,
|
Pagination: *pagination,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import { InputText } from "primevue";
|
|||||||
import Badge from "primevue/badge";
|
import Badge from "primevue/badge";
|
||||||
import Button from "primevue/button";
|
import Button from "primevue/button";
|
||||||
import Column from "primevue/column";
|
import Column from "primevue/column";
|
||||||
import ConfirmDialog from "primevue/confirmdialog";
|
|
||||||
import DataTable from "primevue/datatable";
|
import DataTable from "primevue/datatable";
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
import Dropdown from "primevue/dropdown";
|
import Dropdown from "primevue/dropdown";
|
||||||
import ProgressSpinner from "primevue/progressspinner";
|
import ProgressSpinner from "primevue/progressspinner";
|
||||||
import Toast from "primevue/toast";
|
import Toast from "primevue/toast";
|
||||||
import { useConfirm } from "primevue/useconfirm";
|
import { useConfirm } from "primevue/useconfirm";
|
||||||
import { useToast } from "primevue/usetoast";
|
import { useToast } from "primevue/usetoast";
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, h, onMounted, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -117,11 +117,74 @@ const icons = {
|
|||||||
const getFileIconComponent = (file) => {
|
const getFileIconComponent = (file) => {
|
||||||
return getFileIcon(file, icons);
|
return getFileIcon(file, icons);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add Dialog control refs
|
||||||
|
const previewDialogVisible = ref(false);
|
||||||
|
const previewContent = ref(null);
|
||||||
|
const previewHeader = ref('媒体预览');
|
||||||
|
|
||||||
|
// 检查文件是否可预览
|
||||||
|
const isPreviewable = (file) => {
|
||||||
|
if (!file?.mime_type) return false;
|
||||||
|
const type = file.mime_type.split('/')[0];
|
||||||
|
return ['image', 'video', 'audio'].includes(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预览文件
|
||||||
|
const previewFile = (file) => {
|
||||||
|
if (!file?.mime_type) return;
|
||||||
|
const type = file.mime_type.split('/')[0];
|
||||||
|
const url = mediaService.getMediaPreviewUrl(file.id);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'image':
|
||||||
|
previewContent.value = h('img', {
|
||||||
|
src: url,
|
||||||
|
style: {
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
objectFit: 'contain'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'video':
|
||||||
|
previewContent.value = h('video', {
|
||||||
|
src: url,
|
||||||
|
controls: true,
|
||||||
|
style: {
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '80vh'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'audio':
|
||||||
|
previewContent.value = h('audio', {
|
||||||
|
src: url,
|
||||||
|
controls: true,
|
||||||
|
style: {
|
||||||
|
width: '100%'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewDialogVisible.value = true;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Toast />
|
<Toast />
|
||||||
<ConfirmDialog />
|
<!-- Remove ConfirmDialog -->
|
||||||
|
|
||||||
|
<!-- Add Dialog component -->
|
||||||
|
<Dialog v-model:visible="previewDialogVisible" :modal="true" :header="previewHeader"
|
||||||
|
:style="{ width: '80vw', maxWidth: '1000px' }">
|
||||||
|
<div class="flex justify-center items-center">
|
||||||
|
<component :is="previewContent" />
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex justify-between items-center mb-6 gap-4">
|
<div class="flex justify-between items-center mb-6 gap-4">
|
||||||
@@ -173,17 +236,17 @@ const getFileIconComponent = (file) => {
|
|||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="upload_time" header="上传时间">
|
<Column field="created_at" header="上传时间">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-gray-400"> {{ formatDate(data.upload_time) }}</span>
|
<span class="text-gray-400"> {{ formatDate(data.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="file_size" header="文件大小">
|
<Column field="size" header="文件大小">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
{{ formatFileSize(data.file_size) }}
|
{{ formatFileSize(data.size) }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
@@ -200,8 +263,8 @@ const getFileIconComponent = (file) => {
|
|||||||
<Column header="" :exportable="false" style="min-width: 8rem">
|
<Column header="" :exportable="false" style="min-width: 8rem">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<div class="flex justify-center space-x-2">
|
<div class="flex justify-center space-x-2">
|
||||||
<Button icon="pi pi-eye" rounded text severity="info" @click="previewFile(data)"
|
<Button v-if="isPreviewable(data)" icon="pi pi-eye" rounded text severity="info"
|
||||||
aria-label="Preview" />
|
@click="previewFile(data)" aria-label="Preview" />
|
||||||
<Button icon="pi pi-download" rounded text severity="secondary" @click="downloadFile(data)"
|
<Button icon="pi pi-download" rounded text severity="secondary" @click="downloadFile(data)"
|
||||||
aria-label="Download" />
|
aria-label="Download" />
|
||||||
<Button icon="pi pi-trash" rounded text severity="danger" @click="confirmDelete(data)"
|
<Button icon="pi pi-trash" rounded text severity="danger" @click="confirmDelete(data)"
|
||||||
@@ -224,4 +287,12 @@ const getFileIconComponent = (file) => {
|
|||||||
border-color: #4299e1;
|
border-color: #4299e1;
|
||||||
background-color: rgba(66, 153, 225, 0.05);
|
background-color: rgba(66, 153, 225, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add Dialog custom styles */
|
||||||
|
:deep(.p-dialog-content) {
|
||||||
|
padding: 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user