258 lines
8.9 KiB
Vue
258 lines
8.9 KiB
Vue
<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>
|