Files
quyun/frontend/admin/src/pages/MediaPage.vue
2025-04-11 14:48:36 +08:00

258 lines
8.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { mediaService } from "@/api/mediaService";
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import { InputText } from "primevue";
import Badge from "primevue/badge";
import Button from "primevue/button";
import Column from "primevue/column";
import ConfirmDialog from "primevue/confirmdialog";
import DataTable from "primevue/datatable";
import Dropdown from "primevue/dropdown";
import ProgressSpinner from "primevue/progressspinner";
import Toast from "primevue/toast";
import { useConfirm } from "primevue/useconfirm";
import { useToast } from "primevue/usetoast";
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
// Import useConfirm dynamically to avoid the error when the service is not provided
const confirm = useConfirm();
const toast = useToast();
const router = useRouter();
// Remove upload related refs and methods
const openUploadDialog = () => {
router.push("/medias/uploads");
};
// Media types for filtering
const mediaTypes = ref([
{ name: "所有媒体", value: null },
{ name: "图片", value: "Image" },
{ name: "视频", value: "Video" },
{ name: "文档", value: "Document" },
{ name: "音频", value: "Audio" },
{ name: "PDF", value: "PDF" },
]);
const selectedMediaType = ref(mediaTypes.value[0]);
const globalFilterValue = ref("");
const loading = ref(false);
const filters = ref({
global: { value: null, matchMode: "contains" },
});
// Add pagination state
const first = ref(0);
const rows = ref(10);
const totalRecords = ref(0);
const currentPage = ref(1);
// Add computed for total pages
const totalPages = computed(() => {
return Math.ceil(totalRecords.value / rows.value);
});
// Sample data - in a real app, this would come from an API
const mediaFiles = ref([]);
// Fetch media data
const fetchMediaFiles = async () => {
loading.value = true;
try {
const response = await mediaService.getMedias({
page: currentPage.value,
limit: rows.value,
});
mediaFiles.value = response.data.items;
totalRecords.value = response.data.total;
console.log(totalRecords.value);
} catch (error) {
toast.add({
severity: "error",
summary: "错误",
detail: "加载媒体文件失败",
life: 3000,
});
} finally {
loading.value = false;
}
};
// Add page change handler
const onPage = (event) => {
first.value = event.first;
rows.value = event.rows;
currentPage.value = Math.floor(event.first / event.rows) + 1;
console.log(event);
fetchMediaFiles();
};
onMounted(() => {
fetchMediaFiles();
});
// File type badge severity mapping
const getBadgeSeverity = (fileType) => {
const map = {
Image: "info",
PDF: "danger",
Video: "warning",
Document: "primary",
Audio: "success",
};
return map[fileType] || "info";
};
// File type icon mapping
const getFileIcon = (file) => {
if (file.thumbnailUrl) return null;
return `pi ${file.iconClass}`;
};
// Add helper function to format file size
const formatFileSize = (bytes) => {
if (bytes === 0) return "0 B";
const k = BigInt(1024);
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
// Convert input to BigInt
const bytesValue = BigInt(bytes);
// Calculate the appropriate unit
let i = 0;
let size = bytesValue;
while (size >= k && i < sizes.length - 1) {
size = size / k;
i++;
}
// Convert back to number and format with commas
return `${Number(size).toLocaleString("en-US")} ${sizes[i]}`;
};
// Configure dayjs
dayjs.extend(utc);
dayjs.extend(timezone);
// Add formatDate function
const formatDate = (date) => {
return dayjs.tz(date, 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
};
</script>
<template>
<Toast />
<ConfirmDialog />
<div class="w-full">
<div class="flex justify-between items-center mb-6 gap-4">
<h1 class="text-2xl font-semibold text-gray-800 text-nowrap">媒体库</h1>
<Button class="text-nowrap !px-8" icon="pi pi-upload" label="上传" severity="primary"
@click="openUploadDialog" />
</div>
<!-- Media Table -->
<div class="card mt-10">
<div class="pb-10 flex">
<InputText v-model="globalFilterValue" placeholder="搜索媒体..." class="flex-1" />
</div>
<DataTable v-model:filters="filters" :value="mediaFiles" :paginator="true" v-model:first="first"
v-model:rows="rows" :totalRecords="totalRecords" @page="onPage"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rows-per-page-options="[10, 25, 50]"
current-page-report-template=" {first} {last} {totalRecords} " :lazy="true"
:show-current-page-report="true">
<template #paginatorLeft>
<div class="flex items-center">每页: {{ rows }}</div>
</template>
<template #paginatorRight>
<div class="flex items-center">
{{ currentPage }} {{ totalPages }}
</div>
</template>
<template #empty>
<div class="text-center p-4">未找到媒体文件</div>
</template>
<template #loading>
<div class="flex flex-col items-center justify-center p-4">
<ProgressSpinner style="width: 50px; height: 50px" />
<span class="mt-2">加载中...</span>
</div>
</template>
<Column field="name" header="文件名">
<template #body="{ data }">
<div class="flex items-center">
<div v-if="data.thumbnail_url" class="flex-shrink-0 h-10 w-10 mr-3">
<img class="h-10 w-10 object-cover rounded" :src="data.thumbnail_url"
:alt="data.name" />
</div>
<div v-else
class="flex-shrink-0 h-10 w-10 mr-3 bg-gray-100 rounded flex items-center justify-center">
<i :class="getFileIcon(data)" class="text-2xl"></i>
</div>
<div class="text-sm font-medium text-gray-900">{{ data.name }}</div>
</div>
</template>
</Column>
<Column field="upload_time" header="上传时间">
<template #body="{ data }">
<div class="flex flex-col">
<span class="text-gray-400"> {{ formatDate(data.upload_time) }}</span>
</div>
</template>
</Column>
<Column field="file_size" header="文件大小">
<template #body="{ data }">
{{ formatFileSize(data.file_size) }}
</template>
</Column>
<Column field="media_type" header="文件类型">
<template #body="{ data }">
<Badge :value="data.file_type" :severity="getBadgeSeverity(data.file_type)" />
</template>
<template #filter="{ filterModel, filterCallback }">
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="mediaTypes"
optionLabel="name" optionValue="value" placeholder="全部" class="p-column-filter" showClear />
</template>
</Column>
<Column header="" :exportable="false" style="min-width: 8rem">
<template #body="{ data }">
<div class="flex justify-center space-x-2">
<Button icon="pi pi-eye" rounded text severity="info" @click="previewFile(data)"
aria-label="Preview" />
<Button icon="pi pi-download" rounded text severity="secondary" @click="downloadFile(data)"
aria-label="Download" />
<Button icon="pi pi-trash" rounded text severity="danger" @click="confirmDelete(data)"
aria-label="Delete" />
</div>
</template>
</Column>
</DataTable>
</div>
</div>
</template>
<style scoped>
.p-fileupload {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.p-fileupload:hover {
border-color: #4299e1;
background-color: rgba(66, 153, 225, 0.05);
}
</style>